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.
Files changed (155) hide show
  1. checksums.yaml +7 -0
  2. data/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +227 -0
  3. data/.architecture/decisions/adrs/002-two-tier-memory-architecture.md +322 -0
  4. data/.architecture/decisions/adrs/003-ollama-default-embedding-provider.md +339 -0
  5. data/.architecture/decisions/adrs/004-multi-robot-shared-memory-hive-mind.md +374 -0
  6. data/.architecture/decisions/adrs/005-rag-based-retrieval-with-hybrid-search.md +443 -0
  7. data/.architecture/decisions/adrs/006-context-assembly-strategies.md +444 -0
  8. data/.architecture/decisions/adrs/007-working-memory-eviction-strategy.md +461 -0
  9. data/.architecture/decisions/adrs/008-robot-identification-system.md +550 -0
  10. data/.architecture/decisions/adrs/009-never-forget-explicit-deletion-only.md +570 -0
  11. data/.architecture/decisions/adrs/010-redis-working-memory-rejected.md +323 -0
  12. data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +585 -0
  13. data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +583 -0
  14. data/.architecture/decisions/adrs/013-activerecord-orm-and-many-to-many-tagging.md +299 -0
  15. data/.architecture/decisions/adrs/014-client-side-embedding-generation-workflow.md +569 -0
  16. data/.architecture/decisions/adrs/015-hierarchical-tag-ontology-and-llm-extraction.md +701 -0
  17. data/.architecture/decisions/adrs/016-async-embedding-and-tag-generation.md +694 -0
  18. data/.architecture/members.yml +144 -0
  19. data/.architecture/reviews/2025-10-29-llm-configuration-and-async-processing-review.md +1137 -0
  20. data/.architecture/reviews/initial-system-analysis.md +330 -0
  21. data/.envrc +32 -0
  22. data/.irbrc +145 -0
  23. data/CHANGELOG.md +150 -0
  24. data/COMMITS.md +196 -0
  25. data/LICENSE +21 -0
  26. data/README.md +1347 -0
  27. data/Rakefile +51 -0
  28. data/SETUP.md +268 -0
  29. data/config/database.yml +67 -0
  30. data/db/migrate/20250101000001_enable_extensions.rb +14 -0
  31. data/db/migrate/20250101000002_create_robots.rb +14 -0
  32. data/db/migrate/20250101000003_create_nodes.rb +42 -0
  33. data/db/migrate/20250101000005_create_tags.rb +38 -0
  34. data/db/migrate/20250101000007_add_node_vector_indexes.rb +30 -0
  35. data/db/schema.sql +473 -0
  36. data/db/seed_data/README.md +100 -0
  37. data/db/seed_data/presidents.md +136 -0
  38. data/db/seed_data/states.md +151 -0
  39. data/db/seeds.rb +208 -0
  40. data/dbdoc/README.md +173 -0
  41. data/dbdoc/public.node_stats.md +48 -0
  42. data/dbdoc/public.node_stats.svg +41 -0
  43. data/dbdoc/public.node_tags.md +40 -0
  44. data/dbdoc/public.node_tags.svg +112 -0
  45. data/dbdoc/public.nodes.md +54 -0
  46. data/dbdoc/public.nodes.svg +118 -0
  47. data/dbdoc/public.nodes_tags.md +39 -0
  48. data/dbdoc/public.nodes_tags.svg +112 -0
  49. data/dbdoc/public.ontology_structure.md +48 -0
  50. data/dbdoc/public.ontology_structure.svg +38 -0
  51. data/dbdoc/public.operations_log.md +42 -0
  52. data/dbdoc/public.operations_log.svg +130 -0
  53. data/dbdoc/public.relationships.md +39 -0
  54. data/dbdoc/public.relationships.svg +41 -0
  55. data/dbdoc/public.robot_activity.md +46 -0
  56. data/dbdoc/public.robot_activity.svg +35 -0
  57. data/dbdoc/public.robots.md +35 -0
  58. data/dbdoc/public.robots.svg +90 -0
  59. data/dbdoc/public.schema_migrations.md +29 -0
  60. data/dbdoc/public.schema_migrations.svg +26 -0
  61. data/dbdoc/public.tags.md +35 -0
  62. data/dbdoc/public.tags.svg +60 -0
  63. data/dbdoc/public.topic_relationships.md +45 -0
  64. data/dbdoc/public.topic_relationships.svg +32 -0
  65. data/dbdoc/schema.json +1437 -0
  66. data/dbdoc/schema.svg +154 -0
  67. data/docs/api/database.md +806 -0
  68. data/docs/api/embedding-service.md +532 -0
  69. data/docs/api/htm.md +797 -0
  70. data/docs/api/index.md +259 -0
  71. data/docs/api/long-term-memory.md +1096 -0
  72. data/docs/api/working-memory.md +665 -0
  73. data/docs/architecture/adrs/001-postgresql-timescaledb.md +314 -0
  74. data/docs/architecture/adrs/002-two-tier-memory.md +411 -0
  75. data/docs/architecture/adrs/003-ollama-embeddings.md +421 -0
  76. data/docs/architecture/adrs/004-hive-mind.md +437 -0
  77. data/docs/architecture/adrs/005-rag-retrieval.md +531 -0
  78. data/docs/architecture/adrs/006-context-assembly.md +496 -0
  79. data/docs/architecture/adrs/007-eviction-strategy.md +645 -0
  80. data/docs/architecture/adrs/008-robot-identification.md +625 -0
  81. data/docs/architecture/adrs/009-never-forget.md +648 -0
  82. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +323 -0
  83. data/docs/architecture/adrs/011-pgai-integration.md +494 -0
  84. data/docs/architecture/adrs/index.md +215 -0
  85. data/docs/architecture/hive-mind.md +736 -0
  86. data/docs/architecture/index.md +351 -0
  87. data/docs/architecture/overview.md +538 -0
  88. data/docs/architecture/two-tier-memory.md +873 -0
  89. data/docs/assets/css/custom.css +83 -0
  90. data/docs/assets/images/htm-core-components.svg +63 -0
  91. data/docs/assets/images/htm-database-schema.svg +93 -0
  92. data/docs/assets/images/htm-hive-mind-architecture.svg +125 -0
  93. data/docs/assets/images/htm-importance-scoring-framework.svg +83 -0
  94. data/docs/assets/images/htm-layered-architecture.svg +71 -0
  95. data/docs/assets/images/htm-long-term-memory-architecture.svg +115 -0
  96. data/docs/assets/images/htm-working-memory-architecture.svg +120 -0
  97. data/docs/assets/images/htm.jpg +0 -0
  98. data/docs/assets/images/htm_demo.gif +0 -0
  99. data/docs/assets/js/mathjax.js +18 -0
  100. data/docs/assets/videos/htm_video.mp4 +0 -0
  101. data/docs/database_rake_tasks.md +322 -0
  102. data/docs/development/contributing.md +787 -0
  103. data/docs/development/index.md +336 -0
  104. data/docs/development/schema.md +596 -0
  105. data/docs/development/setup.md +719 -0
  106. data/docs/development/testing.md +819 -0
  107. data/docs/guides/adding-memories.md +824 -0
  108. data/docs/guides/context-assembly.md +1009 -0
  109. data/docs/guides/getting-started.md +577 -0
  110. data/docs/guides/index.md +118 -0
  111. data/docs/guides/long-term-memory.md +941 -0
  112. data/docs/guides/multi-robot.md +866 -0
  113. data/docs/guides/recalling-memories.md +927 -0
  114. data/docs/guides/search-strategies.md +953 -0
  115. data/docs/guides/working-memory.md +717 -0
  116. data/docs/index.md +214 -0
  117. data/docs/installation.md +477 -0
  118. data/docs/multi_framework_support.md +519 -0
  119. data/docs/quick-start.md +655 -0
  120. data/docs/setup_local_database.md +302 -0
  121. data/docs/using_rake_tasks_in_your_app.md +383 -0
  122. data/examples/basic_usage.rb +93 -0
  123. data/examples/cli_app/README.md +317 -0
  124. data/examples/cli_app/htm_cli.rb +270 -0
  125. data/examples/custom_llm_configuration.rb +183 -0
  126. data/examples/example_app/Rakefile +71 -0
  127. data/examples/example_app/app.rb +206 -0
  128. data/examples/sinatra_app/Gemfile +21 -0
  129. data/examples/sinatra_app/app.rb +335 -0
  130. data/lib/htm/active_record_config.rb +113 -0
  131. data/lib/htm/configuration.rb +342 -0
  132. data/lib/htm/database.rb +594 -0
  133. data/lib/htm/embedding_service.rb +115 -0
  134. data/lib/htm/errors.rb +34 -0
  135. data/lib/htm/job_adapter.rb +154 -0
  136. data/lib/htm/jobs/generate_embedding_job.rb +65 -0
  137. data/lib/htm/jobs/generate_tags_job.rb +82 -0
  138. data/lib/htm/long_term_memory.rb +965 -0
  139. data/lib/htm/models/node.rb +109 -0
  140. data/lib/htm/models/node_tag.rb +33 -0
  141. data/lib/htm/models/robot.rb +52 -0
  142. data/lib/htm/models/tag.rb +76 -0
  143. data/lib/htm/railtie.rb +76 -0
  144. data/lib/htm/sinatra.rb +157 -0
  145. data/lib/htm/tag_service.rb +135 -0
  146. data/lib/htm/tasks.rb +38 -0
  147. data/lib/htm/version.rb +5 -0
  148. data/lib/htm/working_memory.rb +182 -0
  149. data/lib/htm.rb +400 -0
  150. data/lib/tasks/db.rake +19 -0
  151. data/lib/tasks/htm.rake +147 -0
  152. data/lib/tasks/jobs.rake +312 -0
  153. data/mkdocs.yml +190 -0
  154. data/scripts/install_local_database.sh +309 -0
  155. 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.