- 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
237 lines
9.3 KiB
Markdown
237 lines
9.3 KiB
Markdown
---
|
|
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/<entity>.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 <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:
|
|
|
|
1. **Run the tests** — execute the test namespace(s) and confirm pass/fail counts
|
|
2. **Count completed behaviors** — run:
|
|
```bash
|
|
grep -E '\| Integration \| \[x\]' <BEHAVIOR_SPEC_PATH> | wc -l
|
|
grep -E '\| Integration \| \[ \]' <BEHAVIOR_SPEC_PATH> | wc -l
|
|
```
|
|
3. **Check unit tests too**:
|
|
```bash
|
|
grep -E '\| Unit.*\| \[x\]' <BEHAVIOR_SPEC_PATH> | wc -l
|
|
grep -E '\| Unit.*\| \[ \]' <BEHAVIOR_SPEC_PATH> | 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`
|