Operations
Testing Strategy
git-pont must be heavily tested because provider integrations break easily and app data loss would be unacceptable.
Test Layers
- Pure model tests
- URL parser tests (including ambiguity)
- Request construction tests
- Response decoding tests
- Pagination tests
- Error mapping tests
- Credential and connection store tests
- Token refresh tests
- Git CLI credential tests
- Provider contract tests with mocked HTTP
- Change submission (orchestration) tests
- Optional live integration tests gated by environment variables
Mocked HTTP
Core defines the injectable HTTPClient (see Architecture → HTTP Abstraction). Tests use a MockHTTPClient that matches requests by method + URL pattern and replays fixture responses, recording every request for assertion.
Tests should not hit the network by default.
Use fixture-driven tests. SwiftPM keeps resources per test target, so each provider target owns its provider fixture files:
libs/swift/Tests/GitPontGitHubTests/Fixtures/GitHub/read-file.json
libs/swift/Tests/GitPontGitHubTests/Fixtures/GitHub/list-directory.json
libs/swift/Tests/GitPontGitLabTests/Fixtures/GitLab/read-file.json
libs/swift/Tests/GitPontGitLabTests/Fixtures/GitLab/list-directory.json
libs/swift/Tests/GitPontForgeTests/Fixtures/Forgejo/read-file.json
libs/swift/Tests/GitPontForgeTests/Fixtures/Forgejo/list-directory.json
Add new provider response cases as JSON fixtures where the payload is reusable across future Android, web, or backend kits. Keep request-only assertions inline when there is no response payload worth sharing.
URL Parser Matrix
Test at least:
- GitHub blob URL
- GitHub blob URL with commit-SHA permalink
- GitHub blob URL with
#L10fragment and query string (stripped) - GitHub raw URL (including
refs/heads/form) - GitHub tree (directory) URL
- GitHub repo URL, with and without
.git - GitHub blob URL with slashed branch →
.ambiguouswith correct candidate order - GitLab blob URL
- GitLab raw URL (
/-/raw/) - GitLab subgroup blob URL
- GitLab self-hosted blob URL (configured instance)
- GitLab repo URL
- Codeberg
src/branch/URL - Codeberg
src/commit/{sha}URL - Forgejo custom source URL
- Gitea custom source URL
- invalid URL
- unsupported host
- custom host without configured instance
Resolution tests: .ambiguous result + mocked branch list → correct (ref, path) chosen; no matching branch → .unsupportedURL.
Pagination Tests
- Multi-page branch and repository lists are concatenated (GitHub
Linkheader, GitLabx-next-page). - Safety cap sets
truncated = trueand stops requesting. per_page=100is sent on list requests.
Conflict Tests
Test that commits include provider version identity:
- GitHub sends
sha - GitLab sends
last_commit_id - Forgejo/Gitea sends
sha
Test provider conflict responses map to GitPontError.conflict(...) with a populated GitConflict.
Test that GitCommitResult.newVersion is populated after a successful commit for every provider (including GitLab's follow-up read).
Change Submission Tests
With mocked HTTP:
.directCommitperforms one commit call..branchAndPullRequestcreates branch, commits, opens PR; PR request uses the right head format..forkAndPullRequestcreates or reuses the connected account's fork, confirms newly created forks, commits to the fork, and opens PR on upstream with fork head (owner:branch/target_project_id)..automaticpicks direct commit whenpermissions.canPushand the branch is not protected; branch+PR when push but protected/PR requested; fork+PR whencanPush == false.- Commit success + PR failure →
.partialSubmissioncarrying the commit result. - Branch success + commit failure →
.partialSubmissionnaming the branch.
Credential and Connection Tests
Use in-memory stores in core tests:
final actor InMemoryCredentialStore: CredentialStore
final actor InMemoryConnectionStore: ConnectionStore
Keychain tests live in GitPontKeychainTests. The default suite verifies the credential payload round trip. A live Keychain save/load/update/delete test is opt-in with GITPONT_RUN_KEYCHAIN_TESTS=1.
Test:
- save/load/delete for both stores
- token not serialized in connection metadata (encode
GitConnection, assert no token substring) - missing credential returns
nil removeConnectiondeletes both metadata and credential
Token Refresh Tests
- Expiring credential triggers refresh before the request; refreshed token is used.
- Concurrent requests against one expiring connection produce exactly one refresh call (assert via mock request count).
- Refreshed credential is saved to the store before dependent requests run.
- Git CLI credential context uses a refreshed token for expiring OAuth credentials.
- Refresh failure does not delete the connection.
- Unexpected provider authentication failure triggers one refresh-and-retry when a refresh token exists.
Git CLI Credential Tests
Test that:
- tokens are placed in environment, not command arguments (assert token value absent from every
argumentsPrefixelement) GIT_TERMINAL_PROMPT=0is set- GitHub, GitLab, and Forgejo contexts use provider-appropriate usernames
- unknown provider returns
.unsupportedCapability - missing connection returns
.missingConnection - multiple connections without
preferredConnectionIDreturns.ambiguousConnection; with it, the preferred one is used
Live Tests
Live tests are disabled by default and require explicit environment variables. The checked-in live suite performs account smoke tests through URLSessionHTTPClient when a token is present:
GITPONT_LIVE_GITHUB_TOKEN
GITPONT_LIVE_GITLAB_TOKEN
GITPONT_LIVE_FORGEJO_TOKEN
GITPONT_LIVE_FORGEJO_BASE_URL # optional, defaults to Codeberg
The checked-in live suite also includes opt-in disposable write cycles. These create a temporary branch, create/read/delete a file under .git-pont-live/, and delete the temporary branch. Configure them only with dedicated test repositories:
GITPONT_LIVE_GITHUB_WRITE_REPO # owner/repo
GITPONT_LIVE_GITHUB_WRITE_BASE_REF # optional, defaults to main
GITPONT_LIVE_GITLAB_WRITE_REPO # namespace/project
GITPONT_LIVE_GITLAB_WRITE_BASE_REF # optional, defaults to main
GITPONT_LIVE_FORGEJO_WRITE_REPO # owner/repo
GITPONT_LIVE_FORGEJO_WRITE_BASE_REF # optional, defaults to main
Never run live write tests in normal CI unless a dedicated test repo is configured.
Use npm run validate:live as the release gate for all configured live provider write cycles. It fails fast when any required token or disposable repository variable is missing.
Safety Tests
Add tests that prevent common data-loss behavior:
- updating an existing file without expected version and without
allowBlindOverwritethrows.conflict(no request sent) - deleting without expected version and without
allowBlindOverwriteis rejected - path traversal segments (
..) are rejected - absolute file paths are rejected as repository paths
- NUL/control characters in paths are rejected
- empty commit message is rejected
- delete requires the explicit delete API; empty-content commit is not a delete
- files above the provider size limit throw
.fileTooLarge, not a decode error