12 KiB
Phase 4 Plan: Full-Codebase Test Coverage
Goal
Build comprehensive automated tests across the full Gitocean codebase so that core behavior is protected by repeatable checks. The test suite should give high confidence that Gitocean works when the external environment is correct: MySQL is available, Git is installed, network ports are free, credentials are valid, and filesystem permissions are sane.
Phase 4 is focused on correctness and regression protection. Product behavior should remain unchanged unless a test exposes a real bug that must be fixed.
Non-goals
- Do not add a Web UI.
- Do not add SSH Git transport.
- Do not replace MySQL.
- Do not redesign APIs or CLI commands for test convenience.
- Do not mock away all meaningful behavior; tests should exercise real logic where practical.
- Do not require MySQL or network services for the default fast unit test suite.
Testing principles
-
Fast tests by default
go test ./...should remain quick and should not require MySQL, long-running servers, or external network access.
-
Integration tests are explicit
- Tests requiring MySQL, Git subprocesses, or real HTTP listeners should be opt-in via build tags or environment variables.
-
Cover logic, not incidental implementation
- Prefer behavior-focused tests over brittle line-by-line implementation tests.
- Preserve public API routes, CLI UX, auth rules, permissions, and storage behavior.
-
Table-driven tests
- Use table-driven tests for validators, parsers, permission checks, route handling, config parsing, output formatting, and error mapping.
-
Hermetic filesystem use
- Use
t.TempDir()for storage, config, bare repositories, worktrees, and backup fixtures.
- Use
-
No production data
- Integration tests must create isolated databases, repos, users, and tokens.
- Tests must clean up after themselves.
-
Regression tests for bugs
- Every bug fixed during Phase 4 should get a regression test before or with the fix.
-
Coverage with intent
- Track coverage using
go test -cover ./.... - Aim for effectively full coverage of pure/domain logic and high meaningful coverage of handlers and CLI parsing.
- External process wrappers and unavoidable OS-specific failure paths may be documented as excluded from practical full coverage.
- Track coverage using
Test tiers
Tier 1: Unit tests
Run with:
go test ./...
Must not require MySQL, a running Gitocean server, or external network access.
Covers:
- Validators
- CLI argument parsing
- Config load/save
- JSON helpers
- MySQL DSN and identifier helpers
- Git route parsing
- Permission helpers where dependencies can be isolated
- Output formatting
- Token/password helper behavior where possible
- Pure backup helper behavior such as path/argument construction
Tier 2: Integration tests
Run explicitly, for example:
go test -tags=integration ./...
or with environment gates such as:
GITOOCEAN_TEST_MYSQL_DSN='user:pass@tcp(127.0.0.1:3306)/gitocean_test?parseTime=true' go test -tags=integration ./...
Covers:
- MySQL migrations
- Register/login/logout/whoami against a real DB
- Token expiry/revoke/prune flows
- Repository create/get/update/delete/search/fork flows
- Collaborator read/write rules
- Pull request create/list/view/close/comment/diff/merge flows
- HTTP Smart Git clone/fetch/push authorization paths
- Backup/restore with real metadata and repository files when tools are present
Tier 3: End-to-end CLI tests
Run explicitly because they spawn commands and may be slower:
go test -tags=e2e ./...
Covers user workflows through the actual CLI binary or app.Run:
init -> server configregister -> login -> whoami -> logoutrepo create -> clone -> push/fetchrepo publishrepo collaborator add/removepr create -> comment -> diff -> mergetoken list/revoke/pruneserver users view/list/add/edit/remove --dsn DSNbackup create/restore
Coverage targets
Required before Phase 4 is considered complete
go test ./...passes.go vet ./...passes.- Unit coverage exists for every internal package.
- Every HTTP API handler has at least one success-path test and meaningful failure-path tests.
- Every CLI command parser has success and failure tests.
- Permission-sensitive behavior has positive and negative tests:
- public read
- private read denied
- owner write
- collaborator read
- collaborator write
- archived repo write denied
- admin-only route allowed/denied
- Auth-sensitive behavior has positive and negative tests:
- valid token
- revoked token
- expired token
- malformed bearer token
- basic auth for Git
- Git HTTP behavior has read/write route coverage.
- Migrations can run against a clean database and are idempotent.
- Coverage reports are generated and reviewed.
Practical coverage expectation
Use coverage as a signal, not a vanity number. The target is full meaningful coverage of project logic:
- Pure functions and parsers: near 100%.
- Domain rules and permission checks: near 100%.
- HTTP handlers: high coverage across success and expected errors.
- CLI dispatch/parsing/output: high coverage.
- External command execution wrappers: covered through integration/e2e where possible; hard-to-trigger OS failure branches may be documented.
Proposed package-by-package work
internal/validate
Add/expand tests for:
- Valid usernames
- Invalid usernames
- Valid repo names
- Invalid repo names
- Valid branches
- Invalid branches
- Reserved names
internal/config
Add/expand tests for:
- Default server URL
- Environment overrides
- Client config path override
- Server config path override
- Client config load/save
- Server config load/save
- Invalid JSON
- Missing token/server cases
internal/dbutil
Add/expand unit tests for:
- MySQL identifier quoting
- Duplicate column error detection
- DSN parsing edge cases where possible
Add integration tests for:
- Auto-create missing database behavior
- Migration idempotency
- Schema contains expected tables/columns/indexes
internal/gitutil
Add unit/integration tests for:
- Bare repository initialization
- Default HEAD points to
refs/heads/main - Branch existence true/false
- Ref listing for branches and tags
- Git command error messages
These tests require Git but should use t.TempDir() only.
internal/httputil
Add tests for:
- Strict JSON decoding rejects unknown fields
- Invalid JSON returns HTTP 400
WriteJSONcontent type/status/bodyWriteErrorbody format
internal/backup
Add unit tests for:
- MySQL CLI arg generation for tcp DSNs
- MySQL CLI arg generation for unix socket DSNs
- Copy directory behavior
- File mode preservation where practical
Add integration tests for:
- Backup archive creation
- Restore into a clean temp storage directory
- Failure when required external tools are unavailable, if practical
internal/app
Split tests by behavior area:
CLI dispatch and parsing
- Unknown root command
- Help output
serverflag parsing defaults/overrides- Repo create/publish parser combinations
- Repo refs/view/search JSON flag behavior
- PR create/list/view parser combinations
- Token/admin command validation
- Server-side direct user management command validation:
gitocean server users --dsn DSN view|list|add|edit|remove - Human-readable output by default
--jsonoutput where supported
HTTP API routing
Use httptest where possible.
- Unknown route returns 404
- Wrong method returns 404 or expected error
- Register/login/logout/me routes
- Token routes
- Admin routes
- Repo routes
- PR routes
- Git HTTP route dispatch
Auth behavior
- First registered user becomes admin
- Later users are not admin
- Login with username
- Login with email
- Invalid password rejected
- Token generated with 7-day expiry
- Logout revokes token
- Expired/revoked token rejected
- Basic auth maps username/token correctly for Git
Repo behavior
- Create public repo
- Create private repo
- Reject invalid repo name
- Reject reserved repo/owner names
- Duplicate repo rejected
- Get public repo anonymously
- Private repo denied anonymously
- Private repo allowed to owner
- Private repo allowed to collaborator according to role
- Update description/visibility/default branch
- Archive/unarchive
- Delete requires force when non-empty if current behavior requires it
- Search public/all/mine scopes
- Fork public repo
- Fork private repo access rules
Pull request behavior
- Create same-repo PR as owner
- Create cross-repo PR from fork
- Reject invalid branches
- Reject unauthorized PR source/target combinations
- List PRs
- View PR
- Close PR
- Comment on PR
- List comments
- Diff PR
- Merge PR
- Reject merge when not authorized
- Reject merge for closed PR
Git HTTP behavior
Use temp bare repos and httptest where possible.
- Parse git paths
- Upload-pack public access
- Upload-pack private denied without auth
- Upload-pack private allowed for owner/collaborator read
- Receive-pack denied without auth
- Receive-pack allowed for owner/write collaborator
- Receive-pack denied for read-only collaborator
- Receive-pack denied when repo is archived
- Required CGI environment variables are set for
git http-backend
Test harness improvements
Phase 4 will likely need small internal test helpers:
internal/app/test_helpers_test.go
internal/testutil/
Potential helpers:
- Create temp server storage
- Create test DB handle or skip integration tests if env var absent
- Run migrations
- Create users/tokens/repos directly for tests
- Create temp Git repos and commits
- Start
httptest.Server - Capture stdout/stderr for CLI output tests
- Build authenticated API requests
Helpers should live in _test.go files unless they are useful production abstractions.
Test data and isolation
- Use unique usernames/repo names per test when a real DB is used.
- Use
t.Cleanupfor filesystem and DB cleanup. - Prefer transactions for DB tests when possible.
- For migration tests, use a uniquely named temporary database.
- Never use real
storage/or real user config files in tests. - Override config paths with environment variables:
GITOOCEAN_CONFIGGITOOCEAN_SERVER_CONFIG
Commands to run during Phase 4
Fast loop:
gofmt -w .
go test ./...
go vet ./...
Coverage:
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
Integration loop:
go test -tags=integration ./...
Optional race checks:
go test -race ./...
Milestones
Milestone 1: Establish coverage baseline
- Run
go test -cover ./.... - Record low-coverage packages.
- Add missing tests for current pure helpers.
- Add
internal/httputil,internal/gitutil,internal/backup, and parser tests where missing.
Milestone 2: Add HTTP/API unit tests
- Build
httptesthelpers. - Add route and handler tests for auth, tokens, repos, PRs, collaborators, and admin.
- Mock or isolate DB state where possible; otherwise mark tests integration.
Milestone 3: Add DB/migration integration tests
- Add opt-in integration test setup for MySQL.
- Test clean migrations and idempotency.
- Test DB-backed auth/repo/PR flows.
Milestone 4: Add Git behavior tests
- Test Git utility functions with temp repos.
- Test HTTP Smart Git permissions.
- Test push/fetch behavior through temp repositories where practical.
Milestone 5: Add CLI workflow tests
- Capture CLI output for human-readable defaults.
- Test
--jsonopt-ins. - Add end-to-end workflows behind explicit
e2etag.
Milestone 6: Coverage review and hardening
- Generate coverage profile.
- Add targeted tests for missed branches.
- Document any intentionally uncovered external/environmental branches.
- Run final checks:
gofmt -w .
go test ./...
go vet ./...
go test -cover ./...
Completion checklist
- Coverage baseline captured.
- Unit tests added for all packages.
- HTTP route and handler tests added.
- CLI parser and output tests added.
- Auth/token behavior tests added.
- Repo permission tests added.
- Collaborator permission tests added.
- PR behavior tests added.
- Git utility and Git HTTP tests added.
- Backup helper tests added.
- MySQL migration integration tests added.
- Optional e2e CLI workflow tests added.
go test ./...passes.go vet ./...passes.- Server-side direct user management commands added and tested.
- Coverage report reviewed.
- Any intentionally uncovered environment-dependent paths documented.