htm 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +227 -0
- data/.architecture/decisions/adrs/002-two-tier-memory-architecture.md +322 -0
- data/.architecture/decisions/adrs/003-ollama-default-embedding-provider.md +339 -0
- data/.architecture/decisions/adrs/004-multi-robot-shared-memory-hive-mind.md +374 -0
- data/.architecture/decisions/adrs/005-rag-based-retrieval-with-hybrid-search.md +443 -0
- data/.architecture/decisions/adrs/006-context-assembly-strategies.md +444 -0
- data/.architecture/decisions/adrs/007-working-memory-eviction-strategy.md +461 -0
- data/.architecture/decisions/adrs/008-robot-identification-system.md +550 -0
- data/.architecture/decisions/adrs/009-never-forget-explicit-deletion-only.md +570 -0
- data/.architecture/decisions/adrs/010-redis-working-memory-rejected.md +323 -0
- data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +585 -0
- data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +583 -0
- data/.architecture/decisions/adrs/013-activerecord-orm-and-many-to-many-tagging.md +299 -0
- data/.architecture/decisions/adrs/014-client-side-embedding-generation-workflow.md +569 -0
- data/.architecture/decisions/adrs/015-hierarchical-tag-ontology-and-llm-extraction.md +701 -0
- data/.architecture/decisions/adrs/016-async-embedding-and-tag-generation.md +694 -0
- data/.architecture/members.yml +144 -0
- data/.architecture/reviews/2025-10-29-llm-configuration-and-async-processing-review.md +1137 -0
- data/.architecture/reviews/initial-system-analysis.md +330 -0
- data/.envrc +32 -0
- data/.irbrc +145 -0
- data/CHANGELOG.md +150 -0
- data/COMMITS.md +196 -0
- data/LICENSE +21 -0
- data/README.md +1347 -0
- data/Rakefile +51 -0
- data/SETUP.md +268 -0
- data/config/database.yml +67 -0
- data/db/migrate/20250101000001_enable_extensions.rb +14 -0
- data/db/migrate/20250101000002_create_robots.rb +14 -0
- data/db/migrate/20250101000003_create_nodes.rb +42 -0
- data/db/migrate/20250101000005_create_tags.rb +38 -0
- data/db/migrate/20250101000007_add_node_vector_indexes.rb +30 -0
- data/db/schema.sql +473 -0
- data/db/seed_data/README.md +100 -0
- data/db/seed_data/presidents.md +136 -0
- data/db/seed_data/states.md +151 -0
- data/db/seeds.rb +208 -0
- data/dbdoc/README.md +173 -0
- data/dbdoc/public.node_stats.md +48 -0
- data/dbdoc/public.node_stats.svg +41 -0
- data/dbdoc/public.node_tags.md +40 -0
- data/dbdoc/public.node_tags.svg +112 -0
- data/dbdoc/public.nodes.md +54 -0
- data/dbdoc/public.nodes.svg +118 -0
- data/dbdoc/public.nodes_tags.md +39 -0
- data/dbdoc/public.nodes_tags.svg +112 -0
- data/dbdoc/public.ontology_structure.md +48 -0
- data/dbdoc/public.ontology_structure.svg +38 -0
- data/dbdoc/public.operations_log.md +42 -0
- data/dbdoc/public.operations_log.svg +130 -0
- data/dbdoc/public.relationships.md +39 -0
- data/dbdoc/public.relationships.svg +41 -0
- data/dbdoc/public.robot_activity.md +46 -0
- data/dbdoc/public.robot_activity.svg +35 -0
- data/dbdoc/public.robots.md +35 -0
- data/dbdoc/public.robots.svg +90 -0
- data/dbdoc/public.schema_migrations.md +29 -0
- data/dbdoc/public.schema_migrations.svg +26 -0
- data/dbdoc/public.tags.md +35 -0
- data/dbdoc/public.tags.svg +60 -0
- data/dbdoc/public.topic_relationships.md +45 -0
- data/dbdoc/public.topic_relationships.svg +32 -0
- data/dbdoc/schema.json +1437 -0
- data/dbdoc/schema.svg +154 -0
- data/docs/api/database.md +806 -0
- data/docs/api/embedding-service.md +532 -0
- data/docs/api/htm.md +797 -0
- data/docs/api/index.md +259 -0
- data/docs/api/long-term-memory.md +1096 -0
- data/docs/api/working-memory.md +665 -0
- data/docs/architecture/adrs/001-postgresql-timescaledb.md +314 -0
- data/docs/architecture/adrs/002-two-tier-memory.md +411 -0
- data/docs/architecture/adrs/003-ollama-embeddings.md +421 -0
- data/docs/architecture/adrs/004-hive-mind.md +437 -0
- data/docs/architecture/adrs/005-rag-retrieval.md +531 -0
- data/docs/architecture/adrs/006-context-assembly.md +496 -0
- data/docs/architecture/adrs/007-eviction-strategy.md +645 -0
- data/docs/architecture/adrs/008-robot-identification.md +625 -0
- data/docs/architecture/adrs/009-never-forget.md +648 -0
- data/docs/architecture/adrs/010-redis-working-memory-rejected.md +323 -0
- data/docs/architecture/adrs/011-pgai-integration.md +494 -0
- data/docs/architecture/adrs/index.md +215 -0
- data/docs/architecture/hive-mind.md +736 -0
- data/docs/architecture/index.md +351 -0
- data/docs/architecture/overview.md +538 -0
- data/docs/architecture/two-tier-memory.md +873 -0
- data/docs/assets/css/custom.css +83 -0
- data/docs/assets/images/htm-core-components.svg +63 -0
- data/docs/assets/images/htm-database-schema.svg +93 -0
- data/docs/assets/images/htm-hive-mind-architecture.svg +125 -0
- data/docs/assets/images/htm-importance-scoring-framework.svg +83 -0
- data/docs/assets/images/htm-layered-architecture.svg +71 -0
- data/docs/assets/images/htm-long-term-memory-architecture.svg +115 -0
- data/docs/assets/images/htm-working-memory-architecture.svg +120 -0
- data/docs/assets/images/htm.jpg +0 -0
- data/docs/assets/images/htm_demo.gif +0 -0
- data/docs/assets/js/mathjax.js +18 -0
- data/docs/assets/videos/htm_video.mp4 +0 -0
- data/docs/database_rake_tasks.md +322 -0
- data/docs/development/contributing.md +787 -0
- data/docs/development/index.md +336 -0
- data/docs/development/schema.md +596 -0
- data/docs/development/setup.md +719 -0
- data/docs/development/testing.md +819 -0
- data/docs/guides/adding-memories.md +824 -0
- data/docs/guides/context-assembly.md +1009 -0
- data/docs/guides/getting-started.md +577 -0
- data/docs/guides/index.md +118 -0
- data/docs/guides/long-term-memory.md +941 -0
- data/docs/guides/multi-robot.md +866 -0
- data/docs/guides/recalling-memories.md +927 -0
- data/docs/guides/search-strategies.md +953 -0
- data/docs/guides/working-memory.md +717 -0
- data/docs/index.md +214 -0
- data/docs/installation.md +477 -0
- data/docs/multi_framework_support.md +519 -0
- data/docs/quick-start.md +655 -0
- data/docs/setup_local_database.md +302 -0
- data/docs/using_rake_tasks_in_your_app.md +383 -0
- data/examples/basic_usage.rb +93 -0
- data/examples/cli_app/README.md +317 -0
- data/examples/cli_app/htm_cli.rb +270 -0
- data/examples/custom_llm_configuration.rb +183 -0
- data/examples/example_app/Rakefile +71 -0
- data/examples/example_app/app.rb +206 -0
- data/examples/sinatra_app/Gemfile +21 -0
- data/examples/sinatra_app/app.rb +335 -0
- data/lib/htm/active_record_config.rb +113 -0
- data/lib/htm/configuration.rb +342 -0
- data/lib/htm/database.rb +594 -0
- data/lib/htm/embedding_service.rb +115 -0
- data/lib/htm/errors.rb +34 -0
- data/lib/htm/job_adapter.rb +154 -0
- data/lib/htm/jobs/generate_embedding_job.rb +65 -0
- data/lib/htm/jobs/generate_tags_job.rb +82 -0
- data/lib/htm/long_term_memory.rb +965 -0
- data/lib/htm/models/node.rb +109 -0
- data/lib/htm/models/node_tag.rb +33 -0
- data/lib/htm/models/robot.rb +52 -0
- data/lib/htm/models/tag.rb +76 -0
- data/lib/htm/railtie.rb +76 -0
- data/lib/htm/sinatra.rb +157 -0
- data/lib/htm/tag_service.rb +135 -0
- data/lib/htm/tasks.rb +38 -0
- data/lib/htm/version.rb +5 -0
- data/lib/htm/working_memory.rb +182 -0
- data/lib/htm.rb +400 -0
- data/lib/tasks/db.rake +19 -0
- data/lib/tasks/htm.rake +147 -0
- data/lib/tasks/jobs.rake +312 -0
- data/mkdocs.yml +190 -0
- data/scripts/install_local_database.sh +309 -0
- metadata +341 -0
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
HTM follows a comprehensive testing philosophy to ensure reliability, correctness, and maintainability. This guide covers everything you need to know about testing HTM.
|
|
4
|
+
|
|
5
|
+
## Testing Philosophy
|
|
6
|
+
|
|
7
|
+
### Core Principles
|
|
8
|
+
|
|
9
|
+
HTM's testing approach is guided by these principles:
|
|
10
|
+
|
|
11
|
+
1. **Test Everything**: Every feature must have corresponding tests
|
|
12
|
+
2. **Test in Isolation**: Methods should be testable independently
|
|
13
|
+
3. **Test Real Scenarios**: Integration tests with actual database
|
|
14
|
+
4. **Test Edge Cases**: Don't just test the happy path
|
|
15
|
+
5. **Keep Tests Fast**: Optimize for quick feedback loops
|
|
16
|
+
6. **Keep Tests Clear**: Tests are documentation
|
|
17
|
+
|
|
18
|
+
### Test-Driven Development
|
|
19
|
+
|
|
20
|
+
We encourage (but don't strictly require) test-driven development:
|
|
21
|
+
|
|
22
|
+
1. **Write the test first** - Define expected behavior
|
|
23
|
+
2. **Watch it fail** - Ensure the test actually tests something
|
|
24
|
+
3. **Implement the feature** - Make the test pass
|
|
25
|
+
4. **Refactor** - Clean up while tests keep you safe
|
|
26
|
+
|
|
27
|
+
## Test Suite Overview
|
|
28
|
+
|
|
29
|
+
HTM uses **Minitest** as its testing framework. The test suite is organized into three categories:
|
|
30
|
+
|
|
31
|
+
### Unit Tests
|
|
32
|
+
|
|
33
|
+
**Purpose**: Test individual methods and classes in isolation
|
|
34
|
+
|
|
35
|
+
**Location**: `test/*_test.rb` (excluding `integration_test.rb`)
|
|
36
|
+
|
|
37
|
+
**Characteristics**:
|
|
38
|
+
|
|
39
|
+
- Fast execution (milliseconds)
|
|
40
|
+
- No database required
|
|
41
|
+
- Mock external dependencies
|
|
42
|
+
- Test logic and behavior
|
|
43
|
+
|
|
44
|
+
**Example**: `test/htm_test.rb`, `test/embedding_service_test.rb`
|
|
45
|
+
|
|
46
|
+
### Integration Tests
|
|
47
|
+
|
|
48
|
+
**Purpose**: Test full workflows with real dependencies
|
|
49
|
+
|
|
50
|
+
**Location**: `test/integration_test.rb`
|
|
51
|
+
|
|
52
|
+
**Characteristics**:
|
|
53
|
+
|
|
54
|
+
- Slower execution (seconds)
|
|
55
|
+
- Requires PostgreSQL
|
|
56
|
+
- Requires Ollama for embeddings
|
|
57
|
+
- Tests real-world scenarios
|
|
58
|
+
- Tests database interactions
|
|
59
|
+
|
|
60
|
+
### Performance Tests
|
|
61
|
+
|
|
62
|
+
**Purpose**: Ensure performance characteristics
|
|
63
|
+
|
|
64
|
+
**Status**: Planned for future implementation
|
|
65
|
+
|
|
66
|
+
**Focus areas**:
|
|
67
|
+
|
|
68
|
+
- Query performance
|
|
69
|
+
- Memory usage
|
|
70
|
+
- Token counting accuracy
|
|
71
|
+
- Embedding generation speed
|
|
72
|
+
|
|
73
|
+
## Running Tests
|
|
74
|
+
|
|
75
|
+
### Run All Tests
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Using Rake (recommended)
|
|
79
|
+
rake test
|
|
80
|
+
|
|
81
|
+
# Using Ruby directly
|
|
82
|
+
ruby -Ilib:test test/**/*_test.rb
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Expected output:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
HTMTest
|
|
89
|
+
test_version_exists PASS (0.00s)
|
|
90
|
+
test_version_format PASS (0.00s)
|
|
91
|
+
test_htm_class_exists PASS (0.00s)
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
IntegrationTest
|
|
95
|
+
test_htm_initializes_with_ollama PASS (0.15s)
|
|
96
|
+
test_add_node_with_embedding PASS (0.32s)
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
Finished in 2.47s
|
|
100
|
+
28 tests, 0 failures, 0 errors, 0 skips
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Run Specific Test File
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Run unit tests only
|
|
107
|
+
ruby test/htm_test.rb
|
|
108
|
+
|
|
109
|
+
# Run embedding service tests
|
|
110
|
+
ruby test/embedding_service_test.rb
|
|
111
|
+
|
|
112
|
+
# Run integration tests
|
|
113
|
+
ruby test/integration_test.rb
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Run Specific Test Method
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Run a single test method
|
|
120
|
+
ruby test/htm_test.rb -n test_version_exists
|
|
121
|
+
|
|
122
|
+
# Run tests matching a pattern
|
|
123
|
+
ruby test/integration_test.rb -n /embedding/
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Run Tests with Verbose Output
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Verbose output
|
|
130
|
+
rake test TESTOPTS="-v"
|
|
131
|
+
|
|
132
|
+
# Show test names as they run
|
|
133
|
+
ruby test/htm_test.rb -v
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Run Tests with Debugging
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Run with debug output
|
|
140
|
+
DEBUG=1 rake test
|
|
141
|
+
|
|
142
|
+
# Run with Ruby debugger
|
|
143
|
+
ruby -r debug test/htm_test.rb
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Test Structure and Organization
|
|
147
|
+
|
|
148
|
+
### Test File Layout
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
test/
|
|
152
|
+
├── test_helper.rb # Shared test configuration
|
|
153
|
+
├── htm_test.rb # Unit tests for HTM class
|
|
154
|
+
├── embedding_service_test.rb # Unit tests for EmbeddingService
|
|
155
|
+
├── integration_test.rb # Integration tests
|
|
156
|
+
└── fixtures/ # Test data (future)
|
|
157
|
+
└── sample_memories.json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Test File Template
|
|
161
|
+
|
|
162
|
+
Every test file follows this structure:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# frozen_string_literal: true
|
|
166
|
+
|
|
167
|
+
require "test_helper"
|
|
168
|
+
|
|
169
|
+
class MyFeatureTest < Minitest::Test
|
|
170
|
+
def setup
|
|
171
|
+
# Runs before each test
|
|
172
|
+
# Initialize test data, mocks, etc.
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def teardown
|
|
176
|
+
# Runs after each test
|
|
177
|
+
# Clean up test data
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def test_something_works
|
|
181
|
+
# Arrange: Set up test data
|
|
182
|
+
input = "test value"
|
|
183
|
+
|
|
184
|
+
# Act: Execute the code being tested
|
|
185
|
+
result = MyClass.some_method(input)
|
|
186
|
+
|
|
187
|
+
# Assert: Verify the results
|
|
188
|
+
assert_equal "expected", result
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def test_handles_edge_case
|
|
192
|
+
# Test edge cases and error conditions
|
|
193
|
+
assert_raises(ArgumentError) do
|
|
194
|
+
MyClass.some_method(nil)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Test Helper Configuration
|
|
201
|
+
|
|
202
|
+
`test/test_helper.rb` provides shared configuration:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
# frozen_string_literal: true
|
|
206
|
+
|
|
207
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
208
|
+
require "htm"
|
|
209
|
+
|
|
210
|
+
require "minitest/autorun"
|
|
211
|
+
require "minitest/reporters"
|
|
212
|
+
|
|
213
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Writing Tests
|
|
217
|
+
|
|
218
|
+
### Unit Test Example
|
|
219
|
+
|
|
220
|
+
Testing a method in isolation:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
class WorkingMemoryTest < Minitest::Test
|
|
224
|
+
def setup
|
|
225
|
+
@memory = HTM::WorkingMemory.new(max_tokens: 1000)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def test_calculates_token_count
|
|
229
|
+
node = { value: "Hello, world!" }
|
|
230
|
+
|
|
231
|
+
result = @memory.calculate_tokens(node)
|
|
232
|
+
|
|
233
|
+
assert_instance_of Integer, result
|
|
234
|
+
assert result > 0
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_rejects_nodes_exceeding_capacity
|
|
238
|
+
@memory = HTM::WorkingMemory.new(max_tokens: 10)
|
|
239
|
+
large_node = { value: "x" * 1000 }
|
|
240
|
+
|
|
241
|
+
assert_raises(HTM::WorkingMemoryFullError) do
|
|
242
|
+
@memory.add_node(large_node)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Integration Test Example
|
|
249
|
+
|
|
250
|
+
Testing with real database:
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
class DatabaseIntegrationTest < Minitest::Test
|
|
254
|
+
def setup
|
|
255
|
+
skip "Database not configured" unless ENV['HTM_DBURL']
|
|
256
|
+
|
|
257
|
+
@htm = HTM.new(
|
|
258
|
+
robot_name: "Test Robot",
|
|
259
|
+
working_memory_size: 128_000,
|
|
260
|
+
embedding_service: :ollama
|
|
261
|
+
)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def teardown
|
|
265
|
+
return unless @htm
|
|
266
|
+
|
|
267
|
+
# Clean up test data
|
|
268
|
+
@htm.forget("test_node_001", confirm: :confirmed) rescue nil
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_adds_and_retrieves_node
|
|
272
|
+
# Add a node
|
|
273
|
+
node_id = @htm.add_node(
|
|
274
|
+
"test_node_001",
|
|
275
|
+
"Test memory content",
|
|
276
|
+
type: :fact,
|
|
277
|
+
importance: 5.0
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
assert_instance_of Integer, node_id
|
|
281
|
+
|
|
282
|
+
# Retrieve it
|
|
283
|
+
node = @htm.retrieve("test_node_001")
|
|
284
|
+
|
|
285
|
+
refute_nil node
|
|
286
|
+
assert_equal "test_node_001", node['key']
|
|
287
|
+
assert_includes node['value'], "Test memory"
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Testing with Mocks and Stubs
|
|
293
|
+
|
|
294
|
+
For testing without external dependencies:
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
class EmbeddingServiceTest < Minitest::Test
|
|
298
|
+
def test_generates_embedding_vector
|
|
299
|
+
service = HTM::EmbeddingService.new(:ollama, model: 'gpt-oss')
|
|
300
|
+
|
|
301
|
+
# Skip if Ollama is not available
|
|
302
|
+
skip "Ollama not running" unless ollama_available?
|
|
303
|
+
|
|
304
|
+
embedding = service.generate_embedding("test text")
|
|
305
|
+
|
|
306
|
+
assert_instance_of Array, embedding
|
|
307
|
+
assert_equal 1536, embedding.length
|
|
308
|
+
assert embedding.all? { |v| v.is_a?(Float) }
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
private
|
|
312
|
+
|
|
313
|
+
def ollama_available?
|
|
314
|
+
require 'net/http'
|
|
315
|
+
uri = URI('http://localhost:11434/api/version')
|
|
316
|
+
response = Net::HTTP.get_response(uri)
|
|
317
|
+
response.is_a?(Net::HTTPSuccess)
|
|
318
|
+
rescue
|
|
319
|
+
false
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Test Fixtures
|
|
325
|
+
|
|
326
|
+
### What are Fixtures?
|
|
327
|
+
|
|
328
|
+
Fixtures are pre-defined test data that can be reused across tests. HTM will use fixtures for complex test scenarios.
|
|
329
|
+
|
|
330
|
+
### Future Fixture Structure
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
# test/fixtures/memories.rb
|
|
334
|
+
module Fixtures
|
|
335
|
+
MEMORIES = {
|
|
336
|
+
fact: {
|
|
337
|
+
key: "user_preference_001",
|
|
338
|
+
value: "User prefers debug_me over puts for debugging",
|
|
339
|
+
type: :fact,
|
|
340
|
+
importance: 7.0
|
|
341
|
+
},
|
|
342
|
+
decision: {
|
|
343
|
+
key: "decision_001",
|
|
344
|
+
value: "We decided to use TimescaleDB for time-series optimization",
|
|
345
|
+
type: :decision,
|
|
346
|
+
importance: 9.0,
|
|
347
|
+
tags: ["database", "architecture"]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
end
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Using Fixtures
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
require_relative 'fixtures/memories'
|
|
357
|
+
|
|
358
|
+
class MemoryTest < Minitest::Test
|
|
359
|
+
def test_stores_fact
|
|
360
|
+
htm = HTM.new(robot_name: "Test")
|
|
361
|
+
fact = Fixtures::MEMORIES[:fact]
|
|
362
|
+
|
|
363
|
+
node_id = htm.add_node(
|
|
364
|
+
fact[:key],
|
|
365
|
+
fact[:value],
|
|
366
|
+
type: fact[:type],
|
|
367
|
+
importance: fact[:importance]
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
assert node_id > 0
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Assertions Reference
|
|
376
|
+
|
|
377
|
+
### Common Assertions
|
|
378
|
+
|
|
379
|
+
Minitest provides many assertion methods:
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
# Equality
|
|
383
|
+
assert_equal expected, actual
|
|
384
|
+
refute_equal unexpected, actual
|
|
385
|
+
|
|
386
|
+
# Truth/falsy
|
|
387
|
+
assert actual
|
|
388
|
+
refute actual
|
|
389
|
+
assert_nil value
|
|
390
|
+
refute_nil value
|
|
391
|
+
|
|
392
|
+
# Type checking
|
|
393
|
+
assert_instance_of String, value
|
|
394
|
+
assert_kind_of Numeric, value
|
|
395
|
+
|
|
396
|
+
# Collections
|
|
397
|
+
assert_includes collection, item
|
|
398
|
+
assert_empty collection
|
|
399
|
+
refute_empty collection
|
|
400
|
+
|
|
401
|
+
# Exceptions
|
|
402
|
+
assert_raises(ErrorClass) { code }
|
|
403
|
+
assert_silent { code }
|
|
404
|
+
|
|
405
|
+
# Matching
|
|
406
|
+
assert_match /pattern/, string
|
|
407
|
+
refute_match /pattern/, string
|
|
408
|
+
|
|
409
|
+
# Comparison
|
|
410
|
+
assert_operator 5, :>, 3
|
|
411
|
+
assert_in_delta 3.14, Math::PI, 0.01
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Custom Assertions
|
|
415
|
+
|
|
416
|
+
You can create custom assertions for HTM-specific checks:
|
|
417
|
+
|
|
418
|
+
```ruby
|
|
419
|
+
module HTMAssertions
|
|
420
|
+
def assert_valid_embedding(embedding)
|
|
421
|
+
assert_instance_of Array, embedding
|
|
422
|
+
assert_equal 1536, embedding.length
|
|
423
|
+
assert embedding.all? { |v| v.is_a?(Float) }
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def assert_valid_node(node)
|
|
427
|
+
assert_instance_of Hash, node
|
|
428
|
+
assert node.key?('id')
|
|
429
|
+
assert node.key?('key')
|
|
430
|
+
assert node.key?('value')
|
|
431
|
+
assert node.key?('type')
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
class MyTest < Minitest::Test
|
|
436
|
+
include HTMAssertions
|
|
437
|
+
|
|
438
|
+
def test_node_structure
|
|
439
|
+
node = create_test_node
|
|
440
|
+
assert_valid_node(node)
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Mocking and Stubbing
|
|
446
|
+
|
|
447
|
+
### When to Mock
|
|
448
|
+
|
|
449
|
+
Mock external dependencies to:
|
|
450
|
+
|
|
451
|
+
- Speed up tests (avoid slow API calls)
|
|
452
|
+
- Test error conditions
|
|
453
|
+
- Isolate the code under test
|
|
454
|
+
- Test without required services
|
|
455
|
+
|
|
456
|
+
### Minitest Mocking
|
|
457
|
+
|
|
458
|
+
Minitest includes built-in mocking:
|
|
459
|
+
|
|
460
|
+
```ruby
|
|
461
|
+
require 'minitest/mock'
|
|
462
|
+
|
|
463
|
+
class ServiceTest < Minitest::Test
|
|
464
|
+
def test_calls_external_api
|
|
465
|
+
mock_client = Minitest::Mock.new
|
|
466
|
+
mock_client.expect :call, "response", ["arg"]
|
|
467
|
+
|
|
468
|
+
service = MyService.new(client: mock_client)
|
|
469
|
+
result = service.process
|
|
470
|
+
|
|
471
|
+
assert_equal "response", result
|
|
472
|
+
mock_client.verify # Ensures expectations were met
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Stubbing Methods
|
|
478
|
+
|
|
479
|
+
Temporarily replace method implementations:
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
class NetworkTest < Minitest::Test
|
|
483
|
+
def test_handles_network_failure
|
|
484
|
+
# Stub a method to simulate failure
|
|
485
|
+
HTM::Database.stub :connected?, false do
|
|
486
|
+
assert_raises(HTM::DatabaseError) do
|
|
487
|
+
htm = HTM.new(robot_name: "Test")
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Test Coverage
|
|
495
|
+
|
|
496
|
+
### Coverage Goals
|
|
497
|
+
|
|
498
|
+
HTM aims for high test coverage:
|
|
499
|
+
|
|
500
|
+
- **Unit tests**: 90%+ line coverage
|
|
501
|
+
- **Integration tests**: Cover all critical paths
|
|
502
|
+
- **Edge cases**: Test error conditions
|
|
503
|
+
- **Documentation**: Tests serve as usage examples
|
|
504
|
+
|
|
505
|
+
### Measuring Coverage (Future)
|
|
506
|
+
|
|
507
|
+
We plan to add SimpleCov for coverage reporting:
|
|
508
|
+
|
|
509
|
+
```ruby
|
|
510
|
+
# test/test_helper.rb (future)
|
|
511
|
+
require 'simplecov'
|
|
512
|
+
SimpleCov.start do
|
|
513
|
+
add_filter '/test/'
|
|
514
|
+
minimum_coverage 90
|
|
515
|
+
end
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Coverage Report
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
# Generate coverage report
|
|
522
|
+
rake test:coverage
|
|
523
|
+
|
|
524
|
+
# View report
|
|
525
|
+
open coverage/index.html
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## Continuous Integration
|
|
529
|
+
|
|
530
|
+
### GitHub Actions (Future)
|
|
531
|
+
|
|
532
|
+
HTM will use GitHub Actions for CI/CD:
|
|
533
|
+
|
|
534
|
+
```yaml
|
|
535
|
+
# .github/workflows/test.yml (future)
|
|
536
|
+
name: Tests
|
|
537
|
+
|
|
538
|
+
on: [push, pull_request]
|
|
539
|
+
|
|
540
|
+
jobs:
|
|
541
|
+
test:
|
|
542
|
+
runs-on: ubuntu-latest
|
|
543
|
+
|
|
544
|
+
services:
|
|
545
|
+
postgres:
|
|
546
|
+
image: timescale/timescaledb-ha:pg17
|
|
547
|
+
env:
|
|
548
|
+
POSTGRES_PASSWORD: testpass
|
|
549
|
+
options: >-
|
|
550
|
+
--health-cmd pg_isready
|
|
551
|
+
--health-interval 10s
|
|
552
|
+
--health-timeout 5s
|
|
553
|
+
--health-retries 5
|
|
554
|
+
|
|
555
|
+
steps:
|
|
556
|
+
- uses: actions/checkout@v3
|
|
557
|
+
- uses: ruby/setup-ruby@v1
|
|
558
|
+
with:
|
|
559
|
+
ruby-version: 3.3
|
|
560
|
+
bundler-cache: true
|
|
561
|
+
- name: Run tests
|
|
562
|
+
run: bundle exec rake test
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### CI Requirements
|
|
566
|
+
|
|
567
|
+
All pull requests must:
|
|
568
|
+
|
|
569
|
+
- Pass all tests (100%)
|
|
570
|
+
- Maintain or improve coverage
|
|
571
|
+
- Pass style checks (future)
|
|
572
|
+
- Pass integration tests
|
|
573
|
+
|
|
574
|
+
## Testing Best Practices
|
|
575
|
+
|
|
576
|
+
### DO: Write Clear Tests
|
|
577
|
+
|
|
578
|
+
```ruby
|
|
579
|
+
# Good: Clear test name and assertions
|
|
580
|
+
def test_working_memory_evicts_least_important_nodes_when_full
|
|
581
|
+
memory = HTM::WorkingMemory.new(max_tokens: 100)
|
|
582
|
+
memory.add_node(key: "important", importance: 9.0, tokens: 50)
|
|
583
|
+
memory.add_node(key: "unimportant", importance: 1.0, tokens: 51)
|
|
584
|
+
|
|
585
|
+
assert memory.contains?("important")
|
|
586
|
+
refute memory.contains?("unimportant")
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Bad: Unclear test
|
|
590
|
+
def test_eviction
|
|
591
|
+
memory = HTM::WorkingMemory.new(max_tokens: 100)
|
|
592
|
+
memory.add_node(key: "a", importance: 9.0, tokens: 50)
|
|
593
|
+
memory.add_node(key: "b", importance: 1.0, tokens: 51)
|
|
594
|
+
assert memory.contains?("a")
|
|
595
|
+
end
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### DO: Test One Thing at a Time
|
|
599
|
+
|
|
600
|
+
```ruby
|
|
601
|
+
# Good: Each test focuses on one behavior
|
|
602
|
+
def test_calculates_token_count
|
|
603
|
+
result = calculate_tokens("hello")
|
|
604
|
+
assert result > 0
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def test_handles_empty_string
|
|
608
|
+
result = calculate_tokens("")
|
|
609
|
+
assert_equal 0, result
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Bad: Testing multiple things
|
|
613
|
+
def test_token_stuff
|
|
614
|
+
assert calculate_tokens("hello") > 0
|
|
615
|
+
assert_equal 0, calculate_tokens("")
|
|
616
|
+
assert_raises(ArgumentError) { calculate_tokens(nil) }
|
|
617
|
+
end
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### DO: Use Descriptive Test Names
|
|
621
|
+
|
|
622
|
+
```ruby
|
|
623
|
+
# Good: Describes what is being tested
|
|
624
|
+
def test_recall_returns_memories_from_specified_timeframe
|
|
625
|
+
def test_forget_requires_confirmation_parameter
|
|
626
|
+
def test_add_node_generates_embedding_automatically
|
|
627
|
+
|
|
628
|
+
# Bad: Vague or unclear
|
|
629
|
+
def test_recall
|
|
630
|
+
def test_forget
|
|
631
|
+
def test_add
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### DO: Clean Up After Tests
|
|
635
|
+
|
|
636
|
+
```ruby
|
|
637
|
+
def setup
|
|
638
|
+
@htm = HTM.new(robot_name: "Test")
|
|
639
|
+
@test_keys = []
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def teardown
|
|
643
|
+
# Clean up any created nodes
|
|
644
|
+
@test_keys.each do |key|
|
|
645
|
+
@htm.forget(key, confirm: :confirmed) rescue nil
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def test_adds_node
|
|
650
|
+
key = "test_#{Time.now.to_i}"
|
|
651
|
+
@test_keys << key
|
|
652
|
+
|
|
653
|
+
@htm.add_node(key, "content", type: :fact)
|
|
654
|
+
# Test continues...
|
|
655
|
+
end
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### DON'T: Rely on Test Order
|
|
659
|
+
|
|
660
|
+
```ruby
|
|
661
|
+
# Bad: Tests depend on each other
|
|
662
|
+
def test_1_creates_node
|
|
663
|
+
@htm.add_node("shared", "value", type: :fact)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def test_2_retrieves_node # Fails if test_1 doesn't run first
|
|
667
|
+
node = @htm.retrieve("shared")
|
|
668
|
+
assert node
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Good: Each test is independent
|
|
672
|
+
def test_creates_node
|
|
673
|
+
@htm.add_node("test_create", "value", type: :fact)
|
|
674
|
+
node = @htm.retrieve("test_create")
|
|
675
|
+
assert node
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def test_retrieves_node
|
|
679
|
+
@htm.add_node("test_retrieve", "value", type: :fact)
|
|
680
|
+
node = @htm.retrieve("test_retrieve")
|
|
681
|
+
assert node
|
|
682
|
+
end
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### DON'T: Use Sleep for Timing
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
# Bad: Flaky test with arbitrary sleep
|
|
689
|
+
def test_async_operation
|
|
690
|
+
start_operation
|
|
691
|
+
sleep 2 # Hope it finishes in 2 seconds
|
|
692
|
+
assert operation_complete?
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# Good: Poll with timeout
|
|
696
|
+
def test_async_operation
|
|
697
|
+
start_operation
|
|
698
|
+
wait_until(timeout: 5) { operation_complete? }
|
|
699
|
+
assert operation_complete?
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def wait_until(timeout: 5)
|
|
703
|
+
start = Time.now
|
|
704
|
+
loop do
|
|
705
|
+
return if yield
|
|
706
|
+
raise "Timeout" if Time.now - start > timeout
|
|
707
|
+
sleep 0.1
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### DON'T: Test Implementation Details
|
|
713
|
+
|
|
714
|
+
```ruby
|
|
715
|
+
# Bad: Testing internal implementation
|
|
716
|
+
def test_uses_specific_sql_query
|
|
717
|
+
assert_match /SELECT \* FROM/, @htm.instance_variable_get(:@last_query)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# Good: Testing behavior/outcome
|
|
721
|
+
def test_retrieves_all_node_fields
|
|
722
|
+
@htm.add_node("key", "value", type: :fact)
|
|
723
|
+
node = @htm.retrieve("key")
|
|
724
|
+
|
|
725
|
+
assert node.key?('id')
|
|
726
|
+
assert node.key?('key')
|
|
727
|
+
assert node.key?('value')
|
|
728
|
+
assert node.key?('type')
|
|
729
|
+
end
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
## Debugging Test Failures
|
|
733
|
+
|
|
734
|
+
### Run Single Test with Verbose Output
|
|
735
|
+
|
|
736
|
+
```bash
|
|
737
|
+
ruby test/htm_test.rb -v -n test_specific_test
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Use debug_me in Tests
|
|
741
|
+
|
|
742
|
+
```ruby
|
|
743
|
+
require 'debug_me'
|
|
744
|
+
|
|
745
|
+
def test_something
|
|
746
|
+
debug_me { [ :input, :expected ] }
|
|
747
|
+
|
|
748
|
+
result = method_under_test(input)
|
|
749
|
+
|
|
750
|
+
debug_me { [ :result ] }
|
|
751
|
+
|
|
752
|
+
assert_equal expected, result
|
|
753
|
+
end
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### Check Test Data
|
|
757
|
+
|
|
758
|
+
```ruby
|
|
759
|
+
def test_database_state
|
|
760
|
+
# Add debugging to inspect state
|
|
761
|
+
pp @htm.memory_stats
|
|
762
|
+
pp @htm.working_memory.inspect
|
|
763
|
+
|
|
764
|
+
# Your test assertions
|
|
765
|
+
assert something
|
|
766
|
+
end
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Use Ruby Debugger
|
|
770
|
+
|
|
771
|
+
```bash
|
|
772
|
+
# Install debugger
|
|
773
|
+
gem install debug
|
|
774
|
+
|
|
775
|
+
# Run test with debugger
|
|
776
|
+
ruby -r debug test/htm_test.rb
|
|
777
|
+
|
|
778
|
+
# Set breakpoints in test
|
|
779
|
+
def test_something
|
|
780
|
+
debugger # Execution will stop here
|
|
781
|
+
result = method_under_test
|
|
782
|
+
assert result
|
|
783
|
+
end
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
## Testing Checklist
|
|
787
|
+
|
|
788
|
+
Before submitting a pull request, ensure:
|
|
789
|
+
|
|
790
|
+
- [ ] All existing tests pass
|
|
791
|
+
- [ ] New features have tests
|
|
792
|
+
- [ ] Edge cases are tested
|
|
793
|
+
- [ ] Error conditions are tested
|
|
794
|
+
- [ ] Tests are clear and well-named
|
|
795
|
+
- [ ] Tests are independent (no order dependency)
|
|
796
|
+
- [ ] Integration tests clean up test data
|
|
797
|
+
- [ ] No skipped tests (unless explicitly documented)
|
|
798
|
+
- [ ] Tests run in reasonable time (<5s for unit, <30s for integration)
|
|
799
|
+
|
|
800
|
+
## Resources
|
|
801
|
+
|
|
802
|
+
### Minitest Documentation
|
|
803
|
+
|
|
804
|
+
- **Official docs**: [https://docs.seattlerb.org/minitest/](https://docs.seattlerb.org/minitest/)
|
|
805
|
+
- **Minitest assertions**: [https://docs.seattlerb.org/minitest/Minitest/Assertions.html](https://docs.seattlerb.org/minitest/Minitest/Assertions.html)
|
|
806
|
+
- **Minitest mocking**: [https://docs.seattlerb.org/minitest/Minitest/Mock.html](https://docs.seattlerb.org/minitest/Minitest/Mock.html)
|
|
807
|
+
|
|
808
|
+
### Testing Guides
|
|
809
|
+
|
|
810
|
+
- **Ruby Testing Guide**: [https://guides.rubyonrails.org/testing.html](https://guides.rubyonrails.org/testing.html)
|
|
811
|
+
- **Better Specs**: [https://www.betterspecs.org/](https://www.betterspecs.org/)
|
|
812
|
+
|
|
813
|
+
## Next Steps
|
|
814
|
+
|
|
815
|
+
- **[Contributing Guide](contributing.md)**: Learn how to submit your tests
|
|
816
|
+
- **[Database Schema](schema.md)**: Understand what you're testing
|
|
817
|
+
- **[Setup Guide](setup.md)**: Get your test environment running
|
|
818
|
+
|
|
819
|
+
Happy testing! Remember: Good tests make better code.
|