- Auth: 30 tests (97 assertions) covering OAuth, sessions, JWT, impersonation, roles - Company: 35 tests (92 assertions) covering profile, 1099, expense reports, permissions - Ledger: 113 tests (148 assertions) covering grid, journal entries, import, reports - Fix existing test failures in running_balance, insights, tx, plaid, graphql - Fix InMemSolrClient to handle Solr query syntax properly - Update behavior docs: auth (42 done), company (32 done), ledger (120 done) - All 478 tests pass with 0 failures, 0 errors
9.3 KiB
name, description
| name | description |
|---|---|
| implement-tests | Guidance for implementing tests given the provided described behaviors |
Implement Tests from Behavior Specs
Use this skill when you need to implement integration and unit tests for behaviors documented in a markdown spec file. This is a structured, iterative workflow that delegates to a subagent and verifies the results.
When to Use
- A behavior spec exists (e.g.,
docs/testing/behaviors/invoice.md) with behaviors marked by test strategy and completion status - Some behaviors are marked
[ ](incomplete) and need test implementation - Behaviors are tagged with test strategies:
Integration,Unit,Unit + Integration,UI
Workflow
Step 1: Read the Spec and Existing Tests
Read the following to understand what needs testing:
- The behavior spec (e.g.,
docs/testing/behaviors/<entity>.md) — identify all behaviors withIntegrationorUnitstrategies marked[ ] - Existing test files — read the main test file(s) to understand current coverage and patterns
Step 2: Identify What to Skip
Before delegating, identify behaviors that are unreasonable to test and note them:
- External services (S3, Textract, Stripe, etc.)
- PDF generation
- UI-only behaviors (require browser automation)
- SSR-only validation that GraphQL bypasses
Communicate these skips to the subagent explicitly.
Step 3: Launch clojure-author Subagent
Use the clojure-author subagent type with a detailed prompt. The prompt should include:
- The list of behaviors to implement
- Explicit list of behaviors to skip (with reasons)
- Instructions to update the behavior document
- Instructions to run tests frequently
- Instructions to document discrepancies
The subagent will already have the test infrastructure, fixtures, helpers, and patterns from this skill's context.
Example prompt template:
You are implementing integration and unit tests for <ENTITY> behaviors in a <LANGUAGE> application.
## Context
- The behavior document is at `<BEHAVIOR_SPEC_PATH>`
- The main integration test file is at `<TEST_FILE_PATH>`
- Test infrastructure, fixtures, helpers, and patterns are documented in the skill loading your context
## Your Task
Implement the REMAINING integration and unit tests for behaviors marked with `[ ]` (incomplete) in `<BEHAVIOR_SPEC_PATH>`. Focus on behaviors with test strategies that include "Integration" or "Unit".
## Behaviors to Implement
<LIST_OF_BEHAVIORS>
## Behaviors to SKIP
<LIST_OF_SKIPS_WITH_REASONS>
## Instructions
1. Read the existing test file to understand patterns
2. Read the behavior document to understand what's expected
3. Implement tests using structured editing tools
4. Update `<BEHAVIOR_SPEC_PATH>` by changing `[ ]` to `[x]` for completed behaviors
5. Run tests frequently
6. Fix any test failures
## CRITICAL RULES
- Use structured editing tools through the clojure-mcp, NOT simple text replacement - don't use the write tool
- Group related behaviors into test functions where it makes sense, but work one test at a time
- Follow existing patterns in the codebase
- When a behavior seems untestable due to external services, skip it and leave `[ ]`
- When tests fail unexpectedly, check the actual implementation and adjust tests to match ACTUAL behavior, not documented behavior
- Document any discrepancies in test comments
- If you run into issues with unbalanced parens, run `clj-repair-parens` to fix them
## When done, report back:
1. Which behaviors you completed
2. Which behaviors you skipped and why
3. Total test count and assertion count added
4. Any discrepancies found between documented and actual behavior
5. Confirm that all tests pass
Step 4: Verify Subagent Work
When the subagent reports completion, verify its work:
- Run the tests — execute the test namespace(s) and confirm pass/fail counts
- Count completed behaviors — run:
grep -E '\| Integration \| \[x\]' <BEHAVIOR_SPEC_PATH> | wc -l grep -E '\| Integration \| \[ \]' <BEHAVIOR_SPEC_PATH> | wc -l - Check unit tests too:
grep -E '\| Unit.*\| \[x\]' <BEHAVIOR_SPEC_PATH> | wc -l grep -E '\| Unit.*\| \[ \]' <BEHAVIOR_SPEC_PATH> | wc -l - Review skipped items — verify the subagent correctly identified untestable behaviors
Step 5: Iterate if Needed
If tests are missing or failing:
- Collect what remains incomplete
- Launch a follow-up subagent with specific feedback
- Repeat up to a maximum of 10 iterations
- Each iteration should narrow the scope to only what remains
Step 6: Mark Skipped Behaviors
For any behaviors that are legitimately untestable, update the behavior document:
# Before
| 13.7 | ... | Integration | [ ] |
# After
| 13.7 | ... | Integration | SKIPPED |
Run a final count to confirm:
- 0 behaviors remain
[ ]for Integration/Unit strategies - Skipped behaviors are clearly marked
Output Checklist
- All feasible integration tests implemented and passing
- All feasible unit tests implemented and passing
- Behavior document updated with
[x]markers - Untestable behaviors marked
SKIPPED - Test count and assertion count reported
- Discrepancies documented (if any)
Test Infrastructure
Fixtures
Use auto-ap.integration.util/wrap-setup as a :each fixture:
(use-fixtures :each wrap-setup)
It creates an in-memory Datomic DB, transacts schema, mocks Solr with InMemSolrClient, and cleans up after each test.
Test Data Helper
Use setup-test-data to create base entities:
(let [{:strs [test-client-id test-vendor-id test-account-id test-bank-account-id]}
(setup-test-data [])]
;; IDs are now available
)
Creates a test expense account, client with check bank account, vendor with default account, and AP account by default. Pass additional entities merged by :db/id:
(setup-test-data [(test-account :db/id "new-account-id")])
Use string tempids and {:strs [...]} destructuring.
User Tokens
(admin-token)— Full admin access(user-token client-id)— User with access to specific client(user-token-no-access)— User with no client access
Datomic Access
@(dc/transact datomic/conn [[:upsert-invoice {...}]])
(dc/pull (dc/db datomic/conn) [:attr] entity-id)
(dc/q '[:find ...] (dc/db datomic/conn) ...)
Key GraphQL Functions (auto-ap.graphql.invoices)
gql-invoices/add-invoice,edit-invoice,void-invoice,void-invoices,unvoid-invoice,unautopay-invoice,bulk-change-invoices
Key Check Functions (auto-ap.graphql.checks)
gql-checks/print-checks-internal,pay-invoices-from-balance,add-handwritten-check
Key SSR Functions (auto-ap.ssr.invoices)
ssr-invoices/fetch-page— returns[invoices count total-outstanding total-amount]ssr-invoices/selected->ids,all-ids-not-locked,redirect-handler
Request format for fetch-page:
{:query-params {:invoice-number "SEARCH" :sort [{:sort-key "date" :asc true}] :per-page 10}
:route-params {:status :invoice-status/unpaid}
:clients [{:db/id test-client-id}]}
Key Route Functions (auto-ap.routes.invoices)
route-invoices/import->invoice,match-vendor,import-uploaded-invoice,validate-invoice
Testing Patterns
Permission Gates
GraphQL checks assert-can-see-client but NOT specific permissions (can?). Permission checks are at the SSR/UI layer. Users with client access can perform operations even with "read-only" role via GraphQL.
Lock Date Behaviors
Create the invoice FIRST, then set :client/locked-until, because add-invoice itself enforces assert-not-locked:
(let [invoice (gql-invoices/add-invoice {:id (admin-token)} {...} nil)]
@(dc/transact datomic/conn [{:db/id test-client-id :client/locked-until #inst "2022-06-01"}])
(is (thrown? Exception (gql-invoices/void-invoice ...))))
Status Changes
(let [result (dc/pull (dc/db datomic/conn) [{:invoice/status [:db/ident]}] invoice-id)]
(is (= :invoice-status/voided (-> result :invoice/status :db/ident))))
Date Handling
#clj-time/date-timefor GraphQL API calls#instfor direct Datomic transactions(time/date-time year month day)for route functions
Structured Clojure Editing
MUST use clojure-mcp structured editing tools — NOT simple text replacement or the write tool.
- Read the file first before editing
clojure-mcp_paren_repair— run proactively after adding multiple deftest blocks, or when tests fail with "EOF while reading"clojure-mcp_clojure_edit— prefer for top-level forms (defn,deftest,ns)clojure-mcp_clojure_edit_replace_sexp— for targeted expression replacements
Tips
- Group related behaviors into single test functions when they share setup (e.g., all sorting behaviors in one test)
- Use actual behavior over documented behavior — if the code doesn't match the spec, test what the code actually does and document the discrepancy
- External services are the #1 skip reason — AWS, Stripe, email, etc. should almost always be skipped
- Permission gates — verify the actual layer where permissions are checked (GraphQL vs SSR vs UI) before writing tests
- Lock dates — create entities first, then set lock dates, because creation itself may enforce
assert-not-locked