Table of Contents
- Sprint 1 Testplan — "Spark"
- 1. Scope
- 2. Test Overview
- 3. Unit Tests (Backend)
- UT-01 — IdeaService.create persists with default status and timestamps
- UT-02 — IdeaService.listForOrg returns newest first
- UT-03 — IdeaController rejects empty title (400)
- UT-04 — IdeaController rejects title > 200 chars (400)
- UT-05 — IdeaController injects @CurrentUser as author
- UT-06 — SparkboardOnboardingHook ADMIN path
- UT-07 — SparkboardOnboardingHook MEMBER path
- UT-08 — Hook idempotency
- UT-09 — SparkboardAdminProperties binding
- 4. Unit Tests (Frontend)
- 5. Integration Tests (Backend)
- IT-01 — Sparkboard Flyway migrations run cleanly
- IT-02 — Sparkboard + plate-auth migrations coexist
- IT-03 — POST /api/ideas → 200 with authenticated context
- IT-04 — GET /api/ideas → 200 with [idea]
- IT-05 — POST /api/ideas → 401 without auth
- IT-06 — OnboardingHook triggers on first plate-auth login (real wiring)
- 6. End-to-End Tests (Playwright)
- E2E-01 — Allowlisted user → sign-in → /ideas
- E2E-02 — Non-allowlisted user → rejection
- E2E-03 — /manifest.json is valid
- E2E-04 — /sw.js registers cleanly
- E2E-05 — Create idea happy path
- E2E-06 — Two users share an org
- 7. Manual Tests
- MT-01 — iPhone Safari install
- MT-02 — Android Chrome install
- MT-03 — 5th account off-allowlist rejection (visible error)
- MT-04 — Gitea Actions deploy on main
- MT-05 — Smoke test
- MT-06 — All 4 humans sign in within one day
- MT-07 — README quickstart on fresh clone
- 8. Test Data
- 9. Environments
- 10. Coverage Mapping
- 11. Out of Scope (Deferred)
- 12. Open Questions (Test-Specific)
- 13. Cross-references
Sprint 1 Testplan — "Spark"
Status: Draft v1 Sprint: 1 — "Spark" Date: 2026-06-24 Owner: Patrick + Roo-Planner Basis: Sprint-1-Plan (chunks 1–4) + Sprint-1-Assessment
1. Scope
This testplan covers the walking-skeleton MVP for Sparkboard: plate-auth wire-up, single Idea entity CRUD-list, PWA install, and Gitea-Actions deploy to TrueNAS. Tests are organised by type:
- UT — Unit tests (JUnit 5 + Mockito on backend, Vitest on frontend)
- IT — Integration tests (
@SpringBootTestwith Testcontainers Postgres on backend) - E2E — End-to-end tests (Playwright against
docker-compose up) - MT — Manual tests (executed by Patrick on real hardware or on
https://sparkboard.plate-software.de)
Tests not in scope: plate-auth's own internals (those are covered by the plate-auth library's own test suite). Sparkboard's testplan only verifies the contract surface and Sparkboard-specific code.
2. Test Overview
| ID | Title | Type | Acceptance |
|---|---|---|---|
| UT-01 | IdeaService.create persists with default status RAW and timestamps |
UT | A4 |
| UT-02 | IdeaService.listForOrg returns newest first |
UT | A4 |
| UT-03 | IdeaController rejects empty title (400) |
UT | A4 |
| UT-04 | IdeaController rejects title > 200 chars (400) |
UT | A4 |
| UT-05 | IdeaController injects @CurrentUser as author |
UT | A4 |
| UT-06 | SparkboardOnboardingHook writes ADMIN row when user email is in sparkboard.admins[] |
UT | A3 |
| UT-07 | SparkboardOnboardingHook writes MEMBER row when user email is NOT in sparkboard.admins[] |
UT | A3 |
| UT-08 | SparkboardOnboardingHook is idempotent (running twice = one row) |
UT | A3 |
| UT-09 | SparkboardAdminProperties binds sparkboard.admins[] from YAML |
UT | A3 |
| UT-10 | Frontend lib/api.ts forwards cookies on server-component calls |
UT | A4 |
| IT-01 | Sparkboard Flyway migrations run cleanly on empty DB | IT | A3, A4 |
| IT-02 | Sparkboard + plate-auth migrations coexist (two history tables) | IT | A3 |
| IT-03 | POST /api/ideas → 200 with authenticated context, idea persisted |
IT | A4 |
| IT-04 | GET /api/ideas → 200 with [idea] returned for the org |
IT | A4 |
| IT-05 | POST /api/ideas → 401 without auth |
IT | A4 |
| IT-06 | OnboardingHook triggers on first plate-auth login (integration with real MembershipService) |
IT | A3 |
| E2E-01 | Allowlisted user → sign-in → /ideas happy path |
E2E | A1, A4 |
| E2E-02 | Non-allowlisted user → sign-in attempt → rejection screen | E2E | A2 |
| E2E-03 | /manifest.json serves valid PWA manifest with theme_color #ea580c |
E2E | A5 |
| E2E-04 | /sw.js registers without console errors |
E2E | A5 |
| E2E-05 | Logged-in user creates idea → redirected to /ideas → idea visible |
E2E | A4 |
| E2E-06 | Two different allowlisted users see each other's ideas (shared org) | E2E | A4 |
| MT-01 | iPhone Safari → "Add to Home Screen" → standalone PWA launches | MT | A5 |
| MT-02 | Android Chrome → install prompt → standalone PWA launches | MT | A5 |
| MT-03 | 5th account (off allowlist) sign-in attempt produces visible error | MT | A2 |
| MT-04 | git push origin main triggers .gitea/workflows/deploy.yml → green |
MT | A6 |
| MT-05 | smoke-test.sh against https://sparkboard.plate-software.de exits 0 |
MT | A6 |
| MT-06 | All 4 humans on allowlist can sign in over the course of one day | MT | A1, A3 |
| MT-07 | README quickstart works on a fresh git clone on a clean machine |
MT | DoD §10.5 |
Total: 27 test cases (10 UT + 6 IT + 6 E2E + 7 MT).
3. Unit Tests (Backend)
UT-01 — IdeaService.create persists with default status and timestamps
- Class:
de.plate.sparkboard.idea.IdeaServiceTest - Setup: Mock
IdeaRepository.saveto return its input. - Given:
CreateIdeaRequest("My idea", "Body", null)and a knownuserId/orgId. - When:
ideaService.create(request, userId, orgId) - Then:
- Returned
Ideahasstatus = IdeaStatus.RAW. createdAtis set (within last 1 sec).updatedAt == createdAt.authorId == userId,orgId == orgId.IdeaRepository.savecalled exactly once.
- Returned
UT-02 — IdeaService.listForOrg returns newest first
- Given: Repo returns 3 ideas with
createdAtof T-2h, T-1h, T-0. - When:
ideaService.listForOrg(orgId) - Then: Result list is
[T-0, T-1h, T-2h](newest first).
UT-03 — IdeaController rejects empty title (400)
- Class:
IdeaControllerTestusing@WebMvcTest. - When:
POST /api/ideaswith body{"title": "", "body": null}and valid auth. - Then: HTTP 400, validation error mentions
title.
UT-04 — IdeaController rejects title > 200 chars (400)
- When:
POST /api/ideaswithtitle = "x".repeat(201). - Then: HTTP 400, validation error mentions
sizeorlength.
UT-05 — IdeaController injects @CurrentUser as author
- When: Authenticated POST with
userId = UandFAMILY_SPARK_ID. - Then: Captured
IdeaService.createargument hasauthorId == U.
UT-06 — SparkboardOnboardingHook ADMIN path
- Class:
de.plate.sparkboard.onboarding.SparkboardOnboardingHookTest - Given:
sparkboard.admins[] = ["patrick@plate-software.de"], login event forpatrick@plate-software.de. - When:
hook.onFirstSignIn(authenticatedUser) - Then:
MembershipService.upsert(userId, "SPARK_ORG", FAMILY_SPARK_ID, "ADMIN")called once.
UT-07 — SparkboardOnboardingHook MEMBER path
- Given:
sparkboard.admins[] = ["patrick@plate-software.de"], login event forkid@example.com. - Then:
MembershipService.upsert(userId, "SPARK_ORG", FAMILY_SPARK_ID, "MEMBER")called once.
UT-08 — Hook idempotency
- When:
hook.onFirstSignIn(user)called twice with same user. - Then:
MembershipService.upsertcalled twice with identical arguments; no exception. (upsertsemantics, not duplicate insert.)
UT-09 — SparkboardAdminProperties binding
- Class:
SparkboardAdminPropertiesTestusingApplicationContextRunner. - Given: YAML
sparkboard.admins: [a@x.de, b@y.de]. - Then:
properties.admins() == List.of("a@x.de", "b@y.de").
4. Unit Tests (Frontend)
UT-10 — lib/api.ts forwards cookies on server-component calls
- Tool: Vitest +
next/headersmock. - Given:
cookies()returns[{name: "next-auth.session-token", value: "abc"}]. - When:
listIdeas()is called from a server component. - Then: The underlying
fetchis called withheaders.Cookie === "next-auth.session-token=abc".
5. Integration Tests (Backend)
IT-01 — Sparkboard Flyway migrations run cleanly
- Class:
SparkboardFlywayMigrationTestusing Testcontainers Postgres. - When: Spring Boot starts with
spring.flyway.locations=classpath:db/migration. - Then:
flyway_schema_historytable exists.spark_orgtable exists with 1 seeded row ('00000000-0000-0000-0000-000000000001', name = "Family Spark").ideastable exists with all columns from V1.- No errors in startup log.
IT-02 — Sparkboard + plate-auth migrations coexist
- Class:
DualFlywayHistoryTest. - Setup: Boot with both
plate-auth-starter(config:plate.auth.flyway.history-table = flyway_schema_history_auth) and Sparkboard migrations. - Then:
- Both
flyway_schema_historyANDflyway_schema_history_authtables exist. - Migrations from both projects applied without collision.
auth_identities(plate-auth) andideas(Sparkboard) both queryable.
- Both
IT-03 — POST /api/ideas → 200 with authenticated context
- Class:
IdeaApiIntegrationTestusing@SpringBootTest(WebEnvironment.RANDOM_PORT)+ a stubbed JWT principal. - Given: Mock authenticated user with
userId = U. - When:
POST /api/ideas { "title": "T", "body": "B" }. - Then:
- HTTP 200, body is the created
IdeaDto. - Postgres now contains one
ideasrow withauthor_id = U,org_id = FAMILY_SPARK_ID,title = "T".
- HTTP 200, body is the created
IT-04 — GET /api/ideas → 200 with [idea]
- Given: One
ideasrow seeded withorg_id = FAMILY_SPARK_ID. - When:
GET /api/ideaswith auth. - Then: HTTP 200, body is a JSON array of length 1 with matching
title.
IT-05 — POST /api/ideas → 401 without auth
- When:
POST /api/ideaswith noAuthorizationheader and no session cookie. - Then: HTTP 401, no DB write.
IT-06 — OnboardingHook triggers on first plate-auth login (real wiring)
- Class:
OnboardingHookIntegrationTest. - Setup: Boot full Spring context with
SparkboardOnboardingHookregistered as a bean. - When: Call
AuthService.handleFirstLogin(authenticatedUser)(the plate-auth integration point). - Then:
membershipstable contains a row with(user_id, 'SPARK_ORG', FAMILY_SPARK_ID, 'MEMBER').- Second invocation does not add a duplicate row.
6. End-to-End Tests (Playwright)
Target:
docker-compose upin CI; smoke-tested against TrueNAS deploy.
E2E-01 — Allowlisted user → sign-in → /ideas
- Steps:
- Navigate to
/. - Click "Sign in with Google".
- Use a mocked OAuth callback OR a real test-google account on allowlist.
- Land on
/ideas.
- Navigate to
- Assert: URL is
/ideas; page renders<h1>Ideas</h1>; "New idea" link visible.
E2E-02 — Non-allowlisted user → rejection
- Steps: Same flow with a Google account whose email is NOT in plate-auth's allowlist.
- Assert: Redirected to
/login?error=access_denied(or equivalent) with a visible message; NOmembershipsrow created in DB.
E2E-03 — /manifest.json is valid
- Steps:
GET /manifest.json. - Assert:
- HTTP 200,
content-type: application/manifest+json(orapplication/json). - JSON parses cleanly.
- Contains
name: "Sparkboard",theme_color: "#ea580c",display: "standalone".
- HTTP 200,
E2E-04 — /sw.js registers cleanly
- Steps: Navigate to
/; wait for service worker registration vianavigator.serviceWorker.ready. - Assert:
- No console errors with severity error.
navigator.serviceWorker.controlleris non-null OR the registration promise resolved.
E2E-05 — Create idea happy path
- Steps:
- Auth as allowlisted user.
- Click "New idea" →
/ideas/new. - Fill title
"E2E test", body"E2E body", submit. - Redirected to
/ideas.
- Assert:
/ideasshows<li>(or similar) containing"E2E test".
E2E-06 — Two users share an org
- Steps:
- User A creates idea
"From A". - User B (different allowlisted account) loads
/ideas.
- User A creates idea
- Assert: User B sees
"From A"in the list (proves shared org viaFAMILY_SPARK_ID).
7. Manual Tests
MT-01 — iPhone Safari install
- Device: Patrick's iPhone.
- Steps:
- Safari →
https://sparkboard.plate-software.de. - Share menu → "Add to Home Screen".
- Confirm icon + name (
Sparkboard). - Launch from home screen.
- Safari →
- Assert: App launches in standalone mode (no Safari chrome); icon is Sparkboard's; title bar reflects the app.
MT-02 — Android Chrome install
- Device: Any Android device.
- Steps:
- Chrome →
https://sparkboard.plate-software.de. - Accept "Install app" banner OR menu → "Install app".
- Launch from launcher.
- Chrome →
- Assert: Same as MT-01 (standalone, correct icon).
MT-03 — 5th account off-allowlist rejection (visible error)
- Steps: Sign in with a Google account NOT on the allowlist.
- Assert: Plain-language rejection message; not a 500 stack trace; user can navigate back to
/login.
MT-04 — Gitea Actions deploy on main
- Steps:
- Commit a trivial change to
main(e.g., README typo fix). - Push.
- Watch
.gitea/workflows/deploy.ymlrun.
- Commit a trivial change to
- Assert: Workflow run shows ✅ Success;
https://sparkboard.plate-software.de/serves the latest commit.
MT-05 — Smoke test
- Steps: Run
deploy/smoke-test.sh https://sparkboard.plate-software.defrom Patrick's laptop. - Assert: Exit code 0; outputs confirmations for
/api/health,/login,/manifest.json,/sw.js.
MT-06 — All 4 humans sign in within one day
- Steps: All 4 family members sign in over a 24-hour window.
- Assert:
membershipstable contains 4 rows for(SPARK_ORG, FAMILY_SPARK_ID). No errors in backend logs.
MT-07 — README quickstart on fresh clone
- Steps:
git clone <sparkboard repo>on a clean machine (or afterdocker system prune+ cleaned~/.m2).- Follow
README.mdstep-by-step. - Reach the point where
http://localhost:3000shows the login page.
- Assert: No undocumented steps required. Any deviation = README fix before Sprint 1 closes.
8. Test Data
8.1 Production seed (V2)
- One row in
spark_org:('00000000-0000-0000-0000-000000000001', 'Family Spark', 'SPARK_ORG', now()). - No seed for
ideasin production. membershipsrows created lazily viaSparkboardOnboardingHookon first login.
8.2 Dev seed (R__dev_seed_ideas.sql, dev profile only)
- 5 dev-only ideas with placeholder text.
- Skipped in
prodprofile via Flyway placeholders or profile-conditional location.
8.3 Test fixtures
IdeaTestBuilder(insrc/test/java) for@WebMvcTestand@SpringBootTest.- Playwright auth state: pre-authenticated session cookie OR mocked OAuth callback.
9. Environments
| Env | URL | Purpose |
|---|---|---|
| Local dev | http://localhost:3000 + http://localhost:8080 |
UT, IT, E2E |
| Gitea Actions CI | ephemeral docker-compose | UT, IT, E2E |
| TrueNAS production | https://sparkboard.plate-software.de |
MT-01..MT-07 |
10. Coverage Mapping
Cross-check that every acceptance criterion has at least 2 covering tests (so a single test failure can't claim acceptance):
| Acceptance | Covered by |
|---|---|
A1 — Allowlisted sign-in → /ideas |
E2E-01, MT-06 |
| A2 — Non-allowlisted rejected | E2E-02, MT-03 |
| A3 — Membership auto-created | UT-06, UT-07, UT-08, IT-06, MT-06 |
| A4 — Idea CRUD-list | UT-01, UT-02, UT-03, UT-04, UT-05, IT-03, IT-04, IT-05, E2E-05, E2E-06 |
| A5 — PWA installable | E2E-03, E2E-04, MT-01, MT-02 |
| A6 — Gitea Actions deploy | MT-04, MT-05 |
Coverage check: ✅ Every A* has ≥ 2 tests. No orphan acceptances.
11. Out of Scope (Deferred)
| Item | Reason | Lands in |
|---|---|---|
| Edit / delete idea | Not in Sprint 1 plan | Sprint 2 testplan |
| Idea detail page | Not in Sprint 1 plan | Sprint 2 testplan |
| Status workflow tests | Status field is RAW-default-only in S1 | Sprint 2 testplan |
| Reactions, comments | Sprint 2/3 features | Sprint 2/3 testplan |
| Offline sync tests | sw.js is stub-only | Sprint 4 testplan |
| Push notification tests | Sprint 4 feature | Sprint 4 testplan |
| Native APK tests | Sprint 5 (Capacitor) | Sprint 5 testplan |
| Load / perf tests | 4 users, no SLA | Never (until Sparkboard scales beyond family) |
| Multi-org tests | Sparkboard is single-org by design | Never |
12. Open Questions (Test-Specific)
| ID | Question | Impact |
|---|---|---|
| QT-01 | Should E2E-01 use a real Google test account or a mocked OAuth callback? | If real, requires a Google test project. If mocked, doesn't validate the full plate-auth chain. Lean: mocked in CI, real in MT-06. |
| QT-02 | Do we run E2E in Gitea Actions or only locally? | Gitea Actions adds CI time but catches deploy regressions. Lean: yes in CI. |
| QT-03 | Should R__dev_seed_ideas.sql be guarded by a Flyway placeholder so it never runs in prod? |
Yes — Flyway placeholder ${env} or separate db/migration-dev/ location. Lean: separate dev-only location in application-dev.yml. |
13. Cross-references
- Sprint-1-Plan — chunks 1–4
- Sprint-1-Assessment — acceptance A1–A6
- Architecture — system diagrams
- Open-Questions — Q01–Q10
- Integration-Guide — plate-auth wire-up walkthrough
End of Sprint 1 Testplan. Status: Draft v1, awaiting GO from Patrick.