From b38f53c6030936b7ca21da5654a43d630fc2e7ba Mon Sep 17 00:00:00 2001 From: Hein Date: Sat, 3 Oct 2026 10:01:48 +0200 Subject: [PATCH] docs(tests): add test coverage plans --- tests/_plans/README.md | 50 ++++++++++++++++++++++++++++++++++++++ tests/_plans/cmd.md | 29 ++++++++++++++++++++++ tests/_plans/diff-merge.md | 29 ++++++++++++++++++++++ tests/_plans/models.md | 18 ++++++++++++++ tests/_plans/pgsql.md | 38 +++++++++++++++++++++++++++++ tests/_plans/sqlexec.md | 20 +++++++++++++++ tests/_plans/sqltypes.md | 14 +++++++++++ tests/_plans/template.md | 23 ++++++++++++++++++ tests/_plans/ui.md | 27 ++++++++++++++++++++ 9 files changed, 248 insertions(+) create mode 100644 tests/_plans/README.md create mode 100644 tests/_plans/cmd.md create mode 100644 tests/_plans/diff-merge.md create mode 100644 tests/_plans/models.md create mode 100644 tests/_plans/pgsql.md create mode 100644 tests/_plans/sqlexec.md create mode 100644 tests/_plans/sqltypes.md create mode 100644 tests/_plans/template.md create mode 100644 tests/_plans/ui.md diff --git a/tests/_plans/README.md b/tests/_plans/README.md new file mode 100644 index 0000000..dbac241 --- /dev/null +++ b/tests/_plans/README.md @@ -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//` 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 ` + - `eval "$(dbtest.sh env postgres)"` sets `RELSPEC_TEST_PG_CONN` (mssql: `RELSPEC_TEST_MSSQL_CONN`, mysql: `RELSPEC_TEST_MYSQL_CONN`) + - `dbtest.sh test [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//` then `go tool cover -func=c.out` +- `make test` before commit diff --git a/tests/_plans/cmd.md b/tests/_plans/cmd.md new file mode 100644 index 0000000..d3ab572 --- /dev/null +++ b/tests/_plans/cmd.md @@ -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. diff --git a/tests/_plans/diff-merge.md b/tests/_plans/diff-merge.md new file mode 100644 index 0000000..d2016a3 --- /dev/null +++ b/tests/_plans/diff-merge.md @@ -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. diff --git a/tests/_plans/models.md b/tests/_plans/models.md new file mode 100644 index 0000000..49bd1f7 --- /dev/null +++ b/tests/_plans/models.md @@ -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). diff --git a/tests/_plans/pgsql.md b/tests/_plans/pgsql.md new file mode 100644 index 0000000..af2a191 --- /dev/null +++ b/tests/_plans/pgsql.md @@ -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 `. + +## pkg/pgsql (87.8%) +- Spot-check uncovered funcs after the above; add keyword/datatype edge cases only. diff --git a/tests/_plans/sqlexec.md b/tests/_plans/sqlexec.md new file mode 100644 index 0000000..08f696f --- /dev/null +++ b/tests/_plans/sqlexec.md @@ -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. diff --git a/tests/_plans/sqltypes.md b/tests/_plans/sqltypes.md new file mode 100644 index 0000000..24fa1be --- /dev/null +++ b/tests/_plans/sqltypes.md @@ -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. diff --git a/tests/_plans/template.md b/tests/_plans/template.md new file mode 100644 index 0000000..4af13aa --- /dev/null +++ b/tests/_plans/template.md @@ -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`. diff --git a/tests/_plans/ui.md b/tests/_plans/ui.md new file mode 100644 index 0000000..126e53c --- /dev/null +++ b/tests/_plans/ui.md @@ -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.