Skip to content

Commit a8a704b

Browse files
authored
Merge pull request #133 from firstsun-dev/test/source-control-e2e-coverage
test: expand source control real-provider E2E coverage
2 parents 8f130b3 + b1d2208 commit a8a704b

15 files changed

Lines changed: 1435 additions & 48 deletions

.github/workflows/ci.yml

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,33 @@ jobs:
5757
- 'package-lock.json'
5858
- '.github/workflows/ci.yml'
5959
60+
# Fast local gate: run cheap deterministic checks in parallel before any
61+
# real-provider E2E spends remote API time. The release-critical reusable CI
62+
# still runs after E2E below; this is only an early failure gate.
63+
preflight:
64+
name: Preflight / ${{ matrix.check }}
65+
runs-on: ubuntu-latest
66+
strategy:
67+
fail-fast: true
68+
max-parallel: 3
69+
matrix:
70+
check: [lint, test, build]
71+
steps:
72+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
73+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
74+
with:
75+
node-version: '22'
76+
cache: npm
77+
- run: npm ci --ignore-scripts
78+
- name: Run ${{ matrix.check }}
79+
run: bash scripts/run-preflight.sh "${{ matrix.check }}"
80+
6081
# Real-provider E2E: one matrix job covering GitHub, GitLab, and Gitea (see
61-
# docs/testing/real-provider-e2e.md).
82+
# docs/testing/real-provider-e2e.md). It starts only after the fast local
83+
# preflight passes, then provider legs run in parallel.
6284
provider-e2e:
6385
name: E2E / ${{ matrix.provider }}
64-
needs: changes
86+
needs: [changes, preflight]
6587
runs-on: [self-hosted, linux, x64, 32gb-ram]
6688
# Runs when sync/provider-relevant paths changed, or unconditionally on
6789
# workflow_dispatch/schedule/a push to main (main always gets the full
@@ -156,6 +178,15 @@ jobs:
156178
# fork PRs get no E2E coverage at all.
157179
if [ "${{ matrix.provider }}" = "gitea" ]; then
158180
run=false
181+
# Make the disabled state explicit in the run log + summary so a
182+
# green "E2E / gitea" job is never mistaken for "Gitea E2E passed".
183+
echo "::notice::Gitea E2E is disabled in CI (runner Docker networking — see TODO below). Suite/harness code passes locally; re-enable by removing this block."
184+
{
185+
echo "### Gitea E2E: disabled"
186+
echo "Reason: runner Docker networking — container provisioning against this runner fleet needs investigation (bridge-IP reachability, health-check timing)."
187+
echo "Suite/harness code is untouched and passes locally (\`npm run test:e2e -- --provider gitea\`). The Gitea infrastructure fix is tracked separately; do not infer three-provider coverage from a green gitea leg."
188+
echo "Re-enable by removing the gitea block in the \"Determine whether this provider leg should run\" step."
189+
} >> "$GITHUB_STEP_SUMMARY"
159190
fi
160191
if [ "${{ github.event_name }}" = "pull_request" ] \
161192
&& [ "${{ matrix.provider }}" != "gitea" ] \
@@ -184,29 +215,24 @@ jobs:
184215
- run: npm ci --ignore-scripts
185216
if: steps.gate.outputs.run == 'true'
186217

187-
# Arrange/Assert/cleanup are Shell + Git (scripts/e2e-harness.sh); Act
188-
# stays production TypeScript (npx vitest). E2E_WORKDIR/E2E_PR_NUMBER/
189-
# E2E_SOURCE_BRANCH are set once at job level (see the job `env:`
190-
# above) so all steps below share the same run state/identity.
191-
- name: Provision isolated branch/container
192-
if: steps.gate.outputs.run == 'true'
193-
env:
194-
E2E_PROVIDER: ${{ matrix.provider }}
195-
run: scripts/e2e-harness.sh provision
196-
197-
- name: Seed baseline fixture
198-
if: steps.gate.outputs.run == 'true'
199-
env:
200-
E2E_PROVIDER: ${{ matrix.provider }}
201-
run: scripts/e2e-harness.sh seed
202-
218+
# One entry point for the whole real-provider E2E flow: scripts/run-e2e.sh
219+
# provisions the isolated branch/container, seeds the baseline fixture,
220+
# runs the suites listed in scripts/e2e-suites.txt (the single source of
221+
# truth — CI and local run the same command, so the suite list is never
222+
# duplicated here), and cleans up via its EXIT trap. New suites are added
223+
# in scripts/e2e-suites.txt only; run-e2e.sh's own forward/reverse checks
224+
# fail the run if a suite file isn't registered (or vice versa).
225+
# E2E_WORKDIR is set by the "Compute run-scoped workdir" step above; the
226+
# job `env:` supplies the provider secrets and run identity
227+
# (E2E_PR_NUMBER/E2E_SOURCE_BRANCH) that run-e2e.sh/e2e-harness.sh consume.
228+
#
203229
# Retried (not just run once): observed failures against the real
204230
# providers include transient runner-network blips unrelated to the
205231
# suite/product code (e.g. a bare `getaddrinfo ENOTFOUND gitlab.com`
206232
# mid-test on 2026-08-14, run 31770197590) that a same-attempt rerun
207-
# simply doesn't reproduce. Safe to retry the whole step from scratch:
208-
# each suite's `runId`/branch paths are randomized per vitest process
209-
# (see e.g. e2e/suites/sync-manager.e2e.test.ts), so a failed
233+
# simply doesn't reproduce. Safe to retry from scratch: run-e2e.sh
234+
# re-provisions a fresh isolated branch each attempt and every suite's
235+
# runId/branch paths are randomized per vitest process, so a failed
210236
# attempt's partial remote state never collides with the retry -- a
211237
# genuine product/test bug still fails identically every attempt and
212238
# exhausts the retries.
@@ -219,19 +245,7 @@ jobs:
219245
timeout_minutes: 15
220246
max_attempts: 3
221247
retry_wait_seconds: 15
222-
command: |
223-
set -a
224-
# shellcheck disable=SC1091
225-
source "$E2E_WORKDIR/e2e.env"
226-
[ -f "$E2E_WORKDIR/e2e.secrets.env" ] && source "$E2E_WORKDIR/e2e.secrets.env"
227-
set +a
228-
npx vitest run -c vitest.e2e.config.ts "e2e/suites/${{ matrix.provider }}.e2e.test.ts" e2e/suites/sync-manager.e2e.test.ts
229-
230-
- name: Independent verification
231-
if: steps.gate.outputs.run == 'true'
232-
env:
233-
E2E_PROVIDER: ${{ matrix.provider }}
234-
run: scripts/e2e-harness.sh verify
248+
command: scripts/run-e2e.sh --provider "${{ matrix.provider }}"
235249

236250
# `if: always()` -- cleanup is best-effort, never a prerequisite for
237251
# the next run (see scripts/e2e-harness.sh's cmd_cleanup and

e2e/shim/fake-vault.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ export class FakeVault {
4040
this.files.set(newPath, content);
4141
}
4242

43+
/** Removes a local file, mirroring Obsidian's vault delete. */
44+
removeLocal(path: string): void {
45+
this.files.delete(path);
46+
}
47+
4348
/** Constructs a real TFile handle for a path already in this vault. */
4449
fileAt(path: string): TFileLike {
4550
return new this.TFile(path);

0 commit comments

Comments
 (0)