--- name: implement-tests description: 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: 1. **The behavior spec** (e.g., `docs/testing/behaviors/.md`) — identify all behaviors with `Integration` or `Unit` strategies marked `[ ]` 2. **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: 1. The list of behaviors to implement 2. Explicit list of behaviors to skip (with reasons) 3. Instructions to update the behavior document 4. Instructions to run tests frequently 5. 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 behaviors in a application. ## Context - The behavior document is at `` - The main integration test file is at `` - 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 ``. Focus on behaviors with test strategies that include "Integration" or "Unit". ## Behaviors to Implement ## Behaviors to SKIP ## 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 `` 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: 1. **Run the tests** — execute the test namespace(s) and confirm pass/fail counts 2. **Count completed behaviors** — run: ```bash grep -E '\| Integration \| \[x\]' | wc -l grep -E '\| Integration \| \[ \]' | wc -l ``` 3. **Check unit tests too**: ```bash grep -E '\| Unit.*\| \[x\]' | wc -l grep -E '\| Unit.*\| \[ \]' | wc -l ``` 4. **Review skipped items** — verify the subagent correctly identified untestable behaviors ### Step 5: Iterate if Needed If tests are missing or failing: 1. Collect what remains incomplete 2. Launch a follow-up subagent with specific feedback 3. Repeat up to a **maximum of 10 iterations** 4. 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: ```bash # 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: ```clojure (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: ```clojure (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`: ```clojure (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 ```clojure @(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`: ```clojure {: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`: ```clojure (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 ```clojure (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-time` for GraphQL API calls - `#inst` for 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. 1. **Read the file first** before editing 2. **`clojure-mcp_paren_repair`** — run proactively after adding multiple deftest blocks, or when tests fail with "EOF while reading" 3. **`clojure-mcp_clojure_edit`** — prefer for top-level forms (`defn`, `deftest`, `ns`) 4. **`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`