docs(tests): add test coverage plans

This commit is contained in:
2026-10-03 10:01:48 +02:00
parent ccba53c494
commit b38f53c603
9 changed files with 248 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
# Test Coverage Plans
Baseline: 51.0% total statements (2026-10-03, after PRs #41-#43).
Scope: pgsql, sqlexec, template, plus non-reader/writer packages. Other readers/writers deferred.
## Order
| # | Plan | Package(s) | Now |
|---|------|-----------|-----|
| 1 | [pgsql.md](pgsql.md) | readers/pgsql, writers/pgsql, pkg/pgsql | 16.0 / 74.0 / 87.8 |
| 2 | [sqlexec.md](sqlexec.md) | writers/sqlexec | 19.4 |
| 3 | [template.md](template.md) | writers/template | 8.5 |
| 4 | [models.md](models.md) | pkg/models | 20.4 |
| 5 | [cmd.md](cmd.md) | cmd/relspec, pkg/jobs | 49.3 / 72.0 |
| 6 | [ui.md](ui.md) | pkg/ui | 3.8 |
| 7 | [diff-merge.md](diff-merge.md) | pkg/diff, pkg/merge | 65.5 / 75.1 |
| 8 | [sqltypes.md](sqltypes.md) | pkg/sqltypes | 67.0 |
## Conventions
- Same package as code under test; table-driven; must pass `-race`.
- Existing data first: `tests/assets/*`, `examples/*.dbml`, `tests/postgres/init.sql`, `tests/postgres/issue21`. Generate new data only where listed under "Data needed".
- New fixtures go in `tests/assets/<format>/` or package `testdata/`.
- Live-DB tests: skip unless the env var is set (pattern in `pkg/readers/pgsql/reader_test.go`). Use `tests/dbtest/dbtest.sh` (podman/docker; see `tests/dbtest/README.md`):
- `dbtest.sh up|down <postgres|mssql|mysql|all>`
- `eval "$(dbtest.sh env postgres)"` sets `RELSPEC_TEST_PG_CONN` (mssql: `RELSPEC_TEST_MSSQL_CONN`, mysql: `RELSPEC_TEST_MYSQL_CONN`)
- `dbtest.sh test <db> [pkgs]` runs up, `go test`, down
- Fixtures for live DBs: postgres `tests/postgres/init.sql`, mssql `test_data/mssql/test_schema.sql`, mysql `tests/dbtest/init/mysql.sql`.
- Prerequisite: working container networking (currently blocked until reboot into matching kernel; `tun` module).
- Prefer pure-function tests over DB tests wherever the logic can be isolated.
- Output assertions must not depend on map order (see memory: map iteration determinism).
## Targets
| Package | Target |
|---------|--------|
| pgsql (all three) | >= 85 |
| sqlexec | >= 80 |
| template | >= 85 |
| models | >= 80 |
| cmd/relspec | >= 65 |
| jobs | >= 85 |
| ui | >= 40 (data ops/pure helpers; screens via smoke tests) |
| diff, merge | >= 85 |
| sqltypes | >= 85 |
## Verify
- `go test -race -coverprofile=c.out ./pkg/<pkg>/` then `go tool cover -func=c.out`
- `make test` before commit
+29
View File
@@ -0,0 +1,29 @@
# Plan: cmd/relspec (49.3%) and pkg/jobs (72.0%)
## Existing
- cmd tests: convert_from_list, diff_sqldir, dry_run (#41), job, merge_from_list, templ_from_list
- jobs: `jobs_test.go`
## cmd/relspec
| Area | Gap | Approach |
|------|-----|----------|
| convert | `readDatabaseForConvert` 22%, `writeDatabase` 36%, `validateWriteTarget` 44%, `loadExtraFields`, `getSchemaNames`, `stderrWarn` | Table-driven per format using `tests/assets`; unsupported format, missing package, bad extra-fields JSON/empty/non-bun, schema filter not found, dctx multi-schema |
| merge | `readDatabaseForMerge` 18%, `writeDatabaseForMerge` 14%, `expandPath`, `parseSkipTables`, `isMergeOutputFormat` | Table-driven formats; globs; skip-list parsing |
| diff | `runDiff`, `readDatabase`, `maskPasswordInDiff` | File-based inputs; password masking cases |
| inspect | `runInspect`, `readDatabaseForInspect`, `filterDatabaseBySchema` | File-based input; schema filter |
| scripts | `runScriptsList` | Use `pkg/readers/sqldir` fixtures; execute path live via dbtest postgres |
| assets | `runAssetsList`, `runAssetsExecute` | List against temp dir; execute live via dbtest postgres |
| edit | `runEdit`, `readDatabaseForEdit`, `writeDatabaseForEdit` | Test read/write helpers only; skip TUI loop |
| report | state dir, load/save state, token, machine id, `submitReport` | Temp HOME; submit against `httptest` server; never hit real endpoint |
| root/main | `printVersionHeader`, `hasSilentFlag` | Pure |
| dry-run | merge/split paths | Add merge dry-run and split dry-run tests (convert covered) |
## pkg/jobs
- `ResolvedLogPolicy`, `Dir`, `validateTemplInput` (0%), `validateOutput` (44%): table-driven valid/invalid job definitions.
- Existing job files: `examples/jobs`.
## Live DB cases (dbtest)
- `runScriptsExecute`, `runAssetsExecute`, job script-exec, `readDatabaseForConvert/Merge/Inspect` for pgsql, and pgsql merge/convert output: `tests/dbtest/dbtest.sh test postgres ./cmd/relspec/`.
- mssql source reads (convert/inspect): `dbtest.sh up mssql`, env `RELSPEC_TEST_MSSQL_CONN`; fixture `test_data/mssql/test_schema.sql`.
- Skip when env var unset.
+29
View File
@@ -0,0 +1,29 @@
# Plan: pkg/diff (65.5%) and pkg/merge (75.1%)
## Existing
- diff: `diff_test.go`, `formatters_test.go`
- merge: `merge_test.go`
## pkg/diff
| Func | Now | Cases |
|------|-----|-------|
| `compareSchemaDetails` | 0% | Description/owner/options changed |
| `compareConstraintDetails`, `normalizeConstraintAction` | 0% | Columns, referenced table, on-update/on-delete variants and case/default normalisation |
| `compareRelationshipDetails` | 0% | Changed endpoints/type |
| `compareViews`, `compareViewDetails` | 0% | Added/removed/changed definition |
| `compareSequences`, `compareSequenceDetails` | 0% | Added/removed/changed increment/min/max/start |
Data: pair `examples/test_schema.dbml` and `test_schema_modified.dbml`; add view/sequence changes in code-built fixtures.
## pkg/merge
| Func | Now | Cases |
|------|-----|-------|
| `mergeSequences`, `cloneSequence` | 33% / 0% | New, existing, conflicting; clone is deep |
| `cloneSchema` | 48% | Views, sequences, scripts, indexes cloned independently |
| `extractTypeParts` | 48% | Precision/scale, arrays, schema-qualified, no modifiers |
| `GetColumnTypeConflictSummary`, `min` | 0% | Limit truncation, zero conflicts |
## Live DB cases (dbtest)
- pgsql live diff/merge against a real DB: `dbtest.sh test postgres`; reuse `tests/postgres/init.sql` as the live side and `examples/test_schema*.dbml` as the desired side.
+18
View File
@@ -0,0 +1,18 @@
# Plan: pkg/models (20.4%)
## Existing
- `directives_test.go`
## Gaps
| File | Funcs | Cases |
|------|-------|-------|
| `models.go` | All `SQLName` methods, `UpdateDate`, `GetPrimaryKey`, `columnLess`, `GetForeignKeys` | Case handling, empty/nil maps, composite PK ordering, FK filtering |
| `models.go` | `Init*` constructors (Database, Schema, Table, Column, Index, Relation, Relationship, Constraint, Script, View, Sequence, Domain, DomainTable, Enum) | Maps/slices non-nil, name set, defaults |
| `sorting.go` | 20 Sort* funcs | By name and by sequence; ties; map variants return sorted slice; input not mutated where documented |
| `flatview.go` | ToFlatColumns, ToFlatTables, ToFlatConstraints, ToFlatRelationships | Multi-schema, empty db, deterministic order |
| `summaryview.go` | ToSummary | Counts across object types |
| `directives.go` | `directiveFromAny` (22%) | Each input type branch, invalid type |
## Data needed
- One shared in-test builder for a multi-schema Database (reuse `tests/assets/dbml/complex.dbml` via reader only if no import cycle; otherwise build in code).
+38
View File
@@ -0,0 +1,38 @@
# Plan: PostgreSQL
## Tooling
- Live tests: `tests/dbtest/dbtest.sh test postgres` (defaults to readers/pgsql, writers/pgsql, writers/sqlexec) or `up postgres` + `eval "$(tests/dbtest/dbtest.sh env postgres)"`.
- Isolation: each live test creates and drops its own schema; shared fixture DB comes from `init.sql`.
## Existing
- Reader tests: `pkg/readers/pgsql/reader_test.go` (live tests skipped without `RELSPEC_TEST_PG_CONN`; pure tests: MapDataType, ParseIndexDefinition, DeriveRelationship, composite FK)
- Writer tests: diff_statements, directives, extensions, generated_column, migration_writer, serial_sequence
- Data: `tests/postgres/init.sql`, `tests/postgres/issue21`, `tests/assets/dbml/*`, `examples/test_schema*.dbml`
## readers/pgsql (16.0%)
| Item | Gap | Approach |
|------|-----|----------|
| `normalizePostgresDefault` (queries.go) | 0% | Pure; table-driven: casts, nextval, functions, quoted literals, NULL |
| `countColumns/Constraints/Indexes` | 0% | Pure; build Database fixtures |
| `ReadDatabase/ReadSchema/ReadTable` | ~0% | Live; run against `init.sql` DB; assert counts, PK/FK/unique/check/index, views, sequences, extensions |
| `query*` (11 funcs) | 0% | Covered via live ReadDatabase; add one live case per object type |
| `close` | 0% | Live; connection released after read and on error |
Data needed: extend `tests/postgres/init.sql` (loaded by dbtest on `up`; apply changes with `dbtest.sh restart postgres`) with a view, sequence, check constraint, partial index, extension, composite FK (verify what already exists first).
## writers/pgsql (74.0%)
| Item | Gap | Approach |
|------|-----|----------|
| `extractTableNameFromCreate`, `extractStatementContext`, `extractSQLStringValue`, `parseQualifiedIdent`, `firstBareIdent`, `firstIdentAfterKeyword`, `stripQuotes`, `buildStmtContext`, `detectStatementType`, `truncateStatement` | 0% | Pure; table-driven; quoted/qualified/unquoted idents, each statement type, long statements |
| `getCurrentTimestamp`, `finishReport`, `writeReport` | 0% | Report written to temp file; JSON shape, counts, failed statements |
| `executeStatements`, `executeDatabaseSQL` | 0% | Live; success, failure with continue-on-error, failure stop, report output |
| `generateLiveDiffStatements` | 28.6% | Live; empty DB, drifted DB, identical DB |
| `currentColumnHasDescription`, `ExecuteCommentColumn` | 0% | Migration writer fixtures with comments added/removed/changed |
| `template_functions.go` `filter`, `mapFunc` | 0% | Pure |
Reuse `tests/integration/failed_statements_example.txt` for failed-statement report cases. Ad-hoc SQL setup: `dbtest.sh exec postgres <file>`.
## pkg/pgsql (87.8%)
- Spot-check uncovered funcs after the above; add keyword/datatype edge cases only.
+20
View File
@@ -0,0 +1,20 @@
# Plan: writers/sqlexec (19.4%)
## Existing
- `writer_test.go`: constructor, nil DB, missing conn string, empty scripts, script sorting, embed directives
## Gaps
| Item | Now | Approach |
|------|-----|----------|
| `Options` | 0% | Trivial getter |
| `WriteDatabase` | 31.2% | Multi-schema; error from one schema aborts; context/connect failure |
| `executeScripts` | 0% | Live via dbtest postgres (`RELSPEC_TEST_PG_CONN`); ordering by priority/sequence, failing script reports script name, empty SQL skipped, transaction/partial-apply behaviour as implemented |
| `WriteSchema` | partial | Connection error path, success path live |
## Data needed
- Small script set (3-4 scripts, mixed priority, one failing) as fixtures; check `tests/assets` and `pkg/readers/sqldir` testdata first.
- Cleanup: each live test uses a throwaway schema and drops it.
## Decision
- Live-only via `tests/dbtest/dbtest.sh test postgres ./pkg/writers/sqlexec/`; no connection interface or mock.
+14
View File
@@ -0,0 +1,14 @@
# Plan: pkg/sqltypes (67.0%)
## Existing
- array types, fromstring, sql types, yaml/xml, struct json, uuid integration tests
## Gaps
| Area | Funcs | Approach |
|------|-------|----------|
| Array types | MarshalYAML/UnmarshalYAML/MarshalXML/UnmarshalXML across each array type; some `UnmarshalJSON/MarshalJSON` | One round-trip test per array type (reuse helper from `sql_types_yaml_xml_test.go`) |
| Scalar types | `Value` (3 types), `MarshalJSON/UnmarshalJSON` for date, `Int64` (31%), `Float64` (40%) | Valid, null, invalid string, overflow |
| Constructors | `SqlTimeStampNow`, `SqlDateNow`, `SqlTimeNow`, `NewSql`, `NewSqlFloat32`, `ToJSONDT` | Assert non-zero/valid and approximately now |
No data needed.
+23
View File
@@ -0,0 +1,23 @@
# Plan: writers/template (8.5%)
## Existing
- `writer_test.go`: deterministic table index values only
## Approach
Pure helper functions; one test file per source file, table-driven. Then render tests through the writer.
| File | Funcs | Cases |
|------|-------|-------|
| `filters.go` | FilterTables, FilterTablesByPattern, FilterColumns, FilterColumnsByType, FilterPrimaryKeys, FilterForeignKeys, FilterUniqueConstraints, FilterCheckConstraints, FilterNullable, FilterNotNull, matchPattern | Empty input, no match, glob patterns, nil maps |
| `formatters.go` | ToJSON, ToJSONPretty, ToYAML, Indent, IndentWith, Escape, EscapeQuotes, Comment, QuoteString, UnquoteString | Empty string, multiline, special chars, marshal failure |
| `loop_helpers.go` | Enumerate, Batch, Chunk, Reverse, First, Last, Skip, Take, Concat, Unique, SortBy, GroupBy, CountIf, getFieldValue, compareValues | Empty, n > len, n <= 0, non-slice input, missing field |
| `safe_access.go` | Get, GetOr, GetPath, GetPathOr, SafeIndex, SafeIndexOr, Has, HasPath, Keys, Merge, Pick, Omit, SliceContains, IndexOf, Pluck | nil, missing key, nested path, out-of-range |
| `string_helpers.go` | ToUpper, ToLower, ToCamelCase, and rest | Empty, snake/kebab/space input, unicode |
| `errors.go` | Error, Unwrap, NewTemplate{Load,Parse,Execute}Error | errors.Is/As, message contents |
| `funcmap.go` | BuildFuncMap | Every registered name resolves and is callable |
| `type_mappers.go`, `template_data.go` | check after above | |
| `writer.go` | WriteDatabase/Schema/Table, modes | Template load/parse/execute error paths; per-table, per-schema, whole-db modes; output to file vs stdout |
## Data needed
- 2-3 small template fixtures in `pkg/writers/template/testdata/` (valid, parse error, execute error).
- Schema input: reuse `tests/assets/dbml/simple.dbml` / `complex.dbml`.
+27
View File
@@ -0,0 +1,27 @@
# Plan: pkg/ui (3.8%)
## Existing
- `object_dataops_test.go` (indexes, views, sequences, scripts, domain assignment)
- Rules: `pkg/ui/ui_rules.md`
## Layers
| Layer | Files | Testable? | Approach |
|-------|-------|-----------|----------|
| Data ops | column_, relation_, domain_, schema_, table_, database_dataops.go | Yes, pure | CRUD tests per file: create, duplicate, update/rename, delete, not-found, bounds, UpdateDate side effects |
| Pure helpers | `sortedKeys`, `schemaLocations`, `tableLocations`, `getColumnNames`, `parseSkipTablesUI`, help-text getters | Yes | Table-driven |
| Kind definitions | `indexKind/viewKind/sequenceKind/scriptKind` | Yes | Assert row builders and form-to-model mapping without rendering |
| Load/save | `loadDatabase`, `saveDatabase`, `createNewDatabase`, `importAndMergeDatabase`, `performMerge` | Partly | Temp files from `tests/assets`; verify format dispatch and error paths; avoid UI dialogs |
| Screens | *_screens.go, dialogs.go, main_menu.go | Yes, via simulation | tview app on tcell SimulationScreen; inject key events; assert navigation, form submit mutates model, cancel leaves it unchanged, delete confirm paths |
## Order
1. Data ops (largest gain, no tview)
2. Pure helpers and kinds
3. Load/save logic
4. Screen tests on simulation screen (menu, lists, forms, confirm dialogs, load/save)
## Decision
- Screen smoke tests via tview simulation screen are in scope (tcell `SimulationScreen`); drive keys/events, assert no panic and expected state.
## Live DB cases (dbtest)
- Load/save and import-merge from a live pgsql source: `dbtest.sh up postgres`; skip when `RELSPEC_TEST_PG_CONN` unset.