Add 8 BDD-style tests for the vendors module covering grid/list operations and vendor merge functionality. Tests follow established patterns from accounts_test.clj and include proper database verification. Tests Implemented: - vendor-grid-loads-with-empty-database - vendor-fetch-ids-returns-correct-structure - vendor-fetch-page-returns-vendors - vendor-hydrate-results-works - vendor-merge-transfers-references - vendor-merge-same-vendor-rejected - vendor-merge-invalid-vendor-handled - vendor-hydration-includes-all-fields Key Implementation Details: - Uses setup-test-data helper with unique temp IDs - Tests focus on public interface (fetch-page, merge-submit) - Follows BDD Given/When/Then pattern - All 8 tests passing (26 assertions) Documentation: - Created implementation plan in docs/plans/ - Documented solution patterns in docs/solutions/ - Created code review todos for future improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
9.1 KiB
module, date, problem_type, component, symptoms, root_cause, resolution_type, severity, tags
| module | date | problem_type | component | symptoms | root_cause | resolution_type | severity | tags | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| auto-ap.ssr.admin | 2026-02-06 | test_failure | testing_framework |
|
test_isolation | test_fix | medium |
|
Datomic Query Patterns in BDD Tests
Problem
When writing BDD-style tests for SSR admin operations, test assertions frequently failed due to improper handling of Datomic query results and entity references. The Datomic API behaves differently than standard Clojure collections, causing tests to fail even when the underlying application logic was correct.
Environment
- Module: auto-ap.ssr.admin
- Date: 2026-02-06
- Affected Component: auto-ap.ssr.admin.accounts-test
- Test Framework: clojure.test
- Database: Datomic
Symptoms
- Assertion failures on entity ID extraction:
(account-id (first accounts))returned[entity-id](a list) instead of just the entity-id - Entity reference resolution failures: Pull queries returned
{:account/type :db/id-12345}(entity reference) instead of{:account/type :account-type/asset}(actual value) - Type mismatch errors: Tests failed when comparing expected numeric code "12345" (string) to actual numeric code 12345 (number)
- Tempid unavailability: Server HTTP responses didn't include Datomic tempids for created entities
What Didn't Work
Attempted Solution 1: Using first on query results
(let [accounts (dc/q '[:find ?e :where [?e :account/name "TestAccount"]] db)]
(is (= expected-id (account-id (first accounts)))))
- Why it failed: Datomic queries return tuples, and
(first accounts)returns a tuple[entity-id](a list form), not just the entity-id
Attempted Solution 2: Direct entity reference in pull
'[:account/type]
- Why it failed: Pull queries return entity references (like
:db/id-12345) for schema attributes, not their actual values
Attempted Solution 3: String comparison for numeric codes
(is (= "12345" (:account/numeric-code account)))
- Why it failed: Account numeric codes are stored as numbers in Datomic, not strings. The comparison failed due to type mismatch
Attempted Solution 4: Checking tempids in server response
(is (some #(= expected-id %) (get-in result [:data :tempids])))
- Why it failed: SSR controllers return HTTP responses with standard fields (status, body, headers), not Datomic internal tempids
Solution
LEARNING #1: Use ffirst to Extract Entity IDs from Datomic Tuples
; ❌ WRONG - Returns [entity-id] (a list form)
(account-id (first accounts))
; ✅ CORRECT - Extracts entity-id from the tuple
(account-id (ffirst accounts))
Explanation: Datomic queries return collections of tuples. Each tuple contains the result values in order. (first accounts) returns the first tuple as a list form [entity-id], which cannot be destructured directly. ffirst applies first twice: first to get the tuple list, second to get the first element of the tuple (the entity-id).
Best practice: Always use ffirst or apply proper destructuring when working with Datomic query results.
LEARNING #2: Include [:db/ident] to Resolve Entity References
; ❌ WRONG - Returns entity reference
'[:account/type]
; ✅ CORRECT - Returns actual enum value
'[:account/type [:db/ident]]
Access pattern:
; Extract the actual enum value from the entity
(:db/ident (:account/type account)) ; Returns :account-type/asset
Explanation: When querying entity attributes that reference other entities (like account/type referencing account-type/asset), Datomic returns the entity ID as a reference. Including [:db/ident] in the pull expression tells Datomic to fetch the actual value identifier, not the entity reference.
Use case: Essential when asserting on enum values or type-safe attributes in tests.
LEARNING #3: Use Numbers for Numeric Codes, Not Strings
; ❌ WRONG - Numeric code stored as number, not string
(is (= "12345" (:account/numeric-code account)))
; ✅ CORRECT - Numeric code is stored as a number
(is (= 12345 (:account/numeric-code account)))
Explanation: Datomic stores numeric attributes as numbers (double), even though they're defined as numeric code strings in the application domain. The database stores them as numbers; the API returns them as numbers.
Best practice: Always use numeric types when asserting on numeric codes, not string equivalents.
LEARNING #4: Query the Database Directly for Verification
; ❌ WRONG - Expected tempids in server response
(let [result (sut/account-save {...})
response (:response result)]
(is (contains? (:data response) :tempids)))
; ✅ CORRECT - Query database to verify entity was created
(let [result (sut/account-save {...})
db (dc/db conn)
accounts (dc/q '[:find ?e :where [?e :account/name "TestAccount"]] db)]
(is (seq accounts))) ; Query directly to verify
Explanation: SSR controllers return HTTP responses without Datomic-specific details. Tempids are internal Datomic identifiers not exposed in HTTP responses. To verify database operations, always query the database directly after the operation.
Best practice: For database-backed operations in tests, query the database after the operation to verify results.
Why This Works
-
What was the ROOT CAUSE of the problem?
- Datomic queries return collections of tuples, not simple collections
- Entity references in Datomic need explicit resolution through
:db/ident - Numeric attributes in Datomic are stored as numbers, not strings
- SSR controllers don't expose Datomic internal state (tempids, internal IDs)
-
Why does the solution address this root cause?
ffirstproperly extracts entity IDs from Datomic tuples- Including
[:db/ident]in pull expressions resolves entity references to their actual values - Using numeric types matches Datomic's storage format
- Querying the database directly accesses the truth source without relying on partial response data
-
What was the underlying issue?
- The Datomic API has specific behaviors that differ from standard Clojure collections
- Entity references are lazy and need explicit resolution
- Database storage types must be matched in test assertions
- SSR architecture doesn't expose internal database details in HTTP responses
- Tests must query the database directly to verify persisted data
Prevention
Test Writing Best Practices
-
Always use
ffirstfor Datomic query results; Standard pattern (let [results (dc/q query-string db) entity-id (ffirst results)] ; Not: (first results) ...) -
Include
[:db/ident]for entity attribute resolution; Standard pattern for enum values '[:attribute [:db/ident]] -
Use correct data types in assertions
; Check attribute types match database (is (instance? Long (:numeric-code account))) ; Not: String -
Query database for verification
; Standard pattern: operation → verify with database query (let [result (sut/create-resource {...}) db (dc/db conn) entity (dc/q '[:find ?e :where [?e :id ?id]] db)] (is (seq entity))) ; Query directly -
Review Datomic-specific behaviors before writing assertions
- Understand that queries return tuples
- Know that entity references need resolution
- Remember numeric type storage
- Accept that SSR responses don't include internal IDs
Code Review Checklist
- Entity IDs extracted with
ffirstfrom Datomic queries - Entity references resolved with
[:db/ident] - Numeric attributes compared as numbers, not strings
- Database queries used for verification, not partial responses
- Datomic-specific behaviors documented in comments
Test Utility Helpers (Recommended)
Consider creating helper functions in your test library to encapsulate these patterns:
(ns auto-ap.ssr.test-helpers
(:require [datomic.api :as dc]))
(defn get-entity-by-attribute [conn attribute value]
"Retrieve entity by attribute-value pair from Datomic database.
Returns entity or nil if not found."
(ffirst
(dc/q '[:find ?e
:where [?e ?attr val]
[val ?attribute ?value]]
(dc/db conn)
attribute value)))
(defn resolve-attribute [entity attribute]
"Resolve an entity reference attribute to its value.
If attribute is a reference, returns :db/ident; otherwise returns value."
(if (map? (attribute entity))
(get-in entity [attribute :db/ident])
(attribute entity)))
Related Issues
No related issues documented yet.
Keywords: atomic-query, datomic, entity-references, test-patterns, database-queries, ffirst, pull-queries, entity-resolution