test(phase6e): acceptance suite, real-Lab rehearsal, and regression fixes for the lock fix
Adds platform/test/e2e-phase6e.js (23 checks): full ROOT 02/03/04 hierarchy/content/assessment-
pool verification, the complete ROOT 01->02->03->04->05 chain proven on one student (each
course's completion unlocking exactly the next), the direct-API lock bypass proven blocked on
both a Phase 6E course pair and a pre-existing unrelated pair, existing ROOT 06+/Capstone/
is_required behavior confirmed unaffected, self-paced no-instructor-deferral scan,
instructor-notes exclusion, RBAC, persistence, isolation, version stability, content validation.
23/23 pass against both a warm and a freshly-reset stack.
Adds platform/test/foundations-helper.js — a shared completeRootFoundations01Through04/
completeThroughRoot06/completeThroughRoot10 helper, extracted once multiple suites needed the
identical real-completion sequence as test setup, rather than duplicating it four times.
Adds infra/scripts/drills/{run,test}-phase6e-foundations-rehearsal — the real Platform API/
Gateway/Provisioner/disposable-Lab/nested-Docker/Git/k3s dress rehearsal: one real Lab task per
new course (nginx inspection, a real merge conflict, a real container-networking fix) plus the
full chain to a genuine ROOT 05 unlock, with k3s reconfirmed healthy and untouched at the end.
13/13 pass against a real, freshly-built stack.
Fixes the regression fallout from the (correct) course-routes.js lock fix: several earlier
suites' own historical shortcuts had accumulated small ordering/scenario gaps the missing
enforcement had been silently covering for — none were platform bugs. e2e-phase6a/6b/6c/6d.js
now inject the real ROOT 01-04 (and where needed ROOT 05/06/10) completion sequence via
foundations-helper.js before touching ROOT 05+ content; e2e-phase6b.js's bypass-student flow now
completes deployment-rollback/job-task/cronjob-task/container-logging-mcq explicitly (real
scenarios added in later phases that its original ALL_STAGES list never included); e2e-phase6a/
6d.js's own ROOT 01/05 completion tests now complete each module fully (through its lab and
reflection) before moving to the next, since those courses' own modules carry real intra-course
prerequisites. Two now-obsolete Phase-6D-era assertions in e2e-phase6d.js (ROOT 02-04 stay
PLANNED / stay vacuously unlocked) are updated to assert the opposite, with a comment explaining
Phase 6E intentionally changed that fact. All four suites re-verified green fresh.
Also fixes a real, reproducible dash-vs-bash bug caught while testing the new ROOT 01
rehearsal's ordering fix: `&>file` misparses under this image's actual /bin/sh (dash), breaking
a later `$!`-based kill — the rehearsal script now writes multi-line completion loops with
explicit status assertions, matching the discipline the other suites already use.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -74,11 +74,15 @@ async function main() {
|
||||
});
|
||||
|
||||
// ── 2/3/4: ROOT 02/03/04 hierarchy — honestly PLANNED, zero modules, not padded ──────────
|
||||
await test('ROOT 02/03/04 are honestly PLANNED with zero modules — not padded to look further along than they are', async () => {
|
||||
await test('ROOT 02/03/04 existed honestly PLANNED at the end of Phase 6D, and are real, authored courses as of Phase 6E', async () => {
|
||||
// Phase 6D deliberately built only ROOT 01 and left 02-04 honestly empty — Phase 6E (see
|
||||
// docs/PHASE6E_ROOT_FOUNDATIONS_COMPLETION.md) authored them for real. This assertion is
|
||||
// updated to reflect that intentional, documented progression rather than re-asserting a
|
||||
// fact Phase 6E was explicitly built to change.
|
||||
for (const id of ['root-02-linux-administration-networking', 'root-03-git-collaborative-development', 'root-04-containers-and-docker']) {
|
||||
const course = courseById(structure, id);
|
||||
assert.equal(course.status, 'planned', `${id} must stay honestly planned`);
|
||||
assert.equal(course.modules.length, 0, `${id} must have zero modules — no fabricated content`);
|
||||
assert.equal(course.status, 'ready', `${id} should now be ready (Phase 6E)`);
|
||||
assert.ok(course.modules.length > 0, `${id} should now have real modules (Phase 6E)`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,18 +124,19 @@ async function main() {
|
||||
});
|
||||
|
||||
await test('completing every ROOT 01 module and its assessment marks the whole course complete', async () => {
|
||||
// ROOT 01's own modules carry real intra-course prerequisites (m02 requires m01 complete,
|
||||
// etc.) — each module must be completed fully, including its lab and reflection, before the
|
||||
// next module unlocks. Completing every module's early activities first and circling back
|
||||
// for labs/reflections later (the original order here) trips the module-lock check.
|
||||
const labs = { m02: 'linux-shell-basics', m03: 'linux-filesystem-navigation', m04: 'linux-files-and-text', m05: 'linux-process-management', m06: 'linux-permissions-foundations' };
|
||||
for (const m of ['m02', 'm03', 'm04', 'm05', 'm06']) {
|
||||
await completeActivity(casey.session, `root-01-${m}-a01-lesson`);
|
||||
await completeActivity(casey.session, `root-01-${m}-a02-demonstration`);
|
||||
await completeActivity(casey.session, `root-01-${m}-a03-knowledge-check`);
|
||||
await completeActivity(casey.session, `root-01-${m}-a04-guided-exercise`);
|
||||
await completeScenario(casey.session, labs[m]);
|
||||
await completeActivity(casey.session, `root-01-${m}-a06-reflection`);
|
||||
}
|
||||
for (const [sid, activity] of [['linux-shell-basics', 'root-01-m02-a06-reflection'], ['linux-filesystem-navigation', 'root-01-m03-a06-reflection'], ['linux-files-and-text', 'root-01-m04-a06-reflection'], ['linux-process-management', 'root-01-m05-a06-reflection']]) {
|
||||
await completeScenario(casey.session, sid);
|
||||
await completeActivity(casey.session, activity);
|
||||
}
|
||||
await completeScenario(casey.session, 'linux-permissions-foundations');
|
||||
await completeActivity(casey.session, 'root-01-m06-a06-reflection');
|
||||
|
||||
const start = await casey.session.request('POST', '/api/assessments/rootnode-linux-foundations-assessment/start');
|
||||
assert.equal(start.status, 201);
|
||||
@@ -157,11 +162,15 @@ async function main() {
|
||||
assert.equal(courseById(body.courses, 'root-02-linux-administration-networking').locked, true, 'ROOT 02 must be locked until ROOT 01 is complete');
|
||||
});
|
||||
|
||||
await test('ROOT 03/04/05 stay unlocked (vacuously satisfied) since their own prerequisite courses have zero content yet', async () => {
|
||||
await test('ROOT 03/04/05 are now genuinely locked for a fresh student (Phase 6E gave 02/03/04 real content)', async () => {
|
||||
// At the end of Phase 6D this was true via vacuous satisfaction (02-04 had zero activities).
|
||||
// Phase 6E authored real content for 02-04, so the SAME chain now genuinely gates for real —
|
||||
// this assertion is updated to prove that real enforcement, not the vacuous case it
|
||||
// originally documented.
|
||||
const { body } = await bob.session.request('GET', `/api/student/programs/${bob.enrollmentId}/courses`);
|
||||
assert.equal(courseById(body.courses, 'root-03-git-collaborative-development').locked, false);
|
||||
assert.equal(courseById(body.courses, 'root-04-containers-and-docker').locked, false);
|
||||
assert.equal(courseById(body.courses, 'root-05-kubernetes-foundations').locked, false);
|
||||
assert.equal(courseById(body.courses, 'root-03-git-collaborative-development').locked, true);
|
||||
assert.equal(courseById(body.courses, 'root-04-containers-and-docker').locked, true);
|
||||
assert.equal(courseById(body.courses, 'root-05-kubernetes-foundations').locked, true);
|
||||
});
|
||||
|
||||
// ── 26/27: existing ROOT 05+ and Capstone state is untouched by this phase ──────────────
|
||||
|
||||
Reference in New Issue
Block a user