Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 50 additions & 91 deletions .github/scripts/__tests__/classroom-setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
* --------------
* 1. Assignment descriptor files (Day 1 and Day 2) -- presence and required
* sections
* 2. Autograding JSON files -- valid JSON, required test fields, non-zero
* 2. Autograder workflows -- every required check workflow exists and
* points, realistic timeouts
* 3. Student Progression Bot workflow -- exists in template, correct triggers,
* required permissions
* 4. Seeding scripts -- all scripts referenced in classroom/README.md exist
* 5. Classroom README deployment guide -- key sections present and accurate
* 6. Template repo readiness -- all files that GitHub Classroom copies are
* present in learning-room/
* 7. Cross-document consistency -- challenge titles in autograding JSON match
* 7. Cross-document consistency -- challenge titles in autograder workflows match
* those in the assignment descriptor and challenge templates
* 8. Actions permissions -- workflow files in the template all declare the
* required write permissions for Aria and the Progression Bot
Expand All @@ -42,8 +42,6 @@ const scriptsDir = path.join(repoRoot, 'scripts/classroom');

const day1DescriptorPath = path.join(classroomDir, 'assignment-day1-you-belong-here.md');
const day2DescriptorPath = path.join(classroomDir, 'assignment-day2-you-can-build-this.md');
const autogradingDay1Path = path.join(classroomDir, 'autograding-day1.json');
const autogradingDay2Path = path.join(classroomDir, 'autograding-day2.json');
const classroomReadmePath = path.join(classroomDir, 'README.md');
const teardownPath = path.join(classroomDir, 'teardown-checklist.md');
const gradingGuidePath = path.join(classroomDir, 'grading-guide.md');
Expand Down Expand Up @@ -80,8 +78,6 @@ test('all required classroom deployment files exist', () => {
const required = [
[day1DescriptorPath, 'assignment-day1-you-belong-here.md'],
[day2DescriptorPath, 'assignment-day2-you-can-build-this.md'],
[autogradingDay1Path, 'autograding-day1.json'],
[autogradingDay2Path, 'autograding-day2.json'],
[classroomReadmePath, 'classroom/README.md'],
[teardownPath, 'teardown-checklist.md'],
[gradingGuidePath, 'grading-guide.md'],
Expand Down Expand Up @@ -117,104 +113,63 @@ test('all seeding scripts referenced in classroom README exist', () => {
});

// ---------------------------------------------------------------------------
// 2. Autograding JSON -- structure and field completeness
// 2. Autograder workflows -- the workflow files Classroom-style checks now
// live in. Each Day 1 / Day 2 check is its own GitHub Actions workflow
// inside the learning-room template (see admin/classroom/autograding-setup.md).
// ---------------------------------------------------------------------------

function validateAutogradingJson(filePath, label, expectedMinTests) {
assertFileExists(filePath, label);
const tests = readJson(filePath);

assert.ok(Array.isArray(tests), `${label}: must be a JSON array`);
assert.ok(
tests.length >= expectedMinTests,
`${label}: expected at least ${expectedMinTests} tests, found ${tests.length}`
);

const requiredFields = ['test_name', 'run', 'comparison', 'timeout', 'points'];
tests.forEach((entry, i) => {
requiredFields.forEach(field => {
assert.ok(
Object.prototype.hasOwnProperty.call(entry, field),
`${label}[${i}]: missing required field "${field}" in test "${entry.test_name || i}"`
);
});

assert.ok(
typeof entry.points === 'number' && entry.points > 0,
`${label}[${i}]: "points" must be a positive number in "${entry.test_name}"`
);

assert.ok(
typeof entry.timeout === 'number' && entry.timeout >= 5 && entry.timeout <= 300,
`${label}[${i}]: "timeout" must be between 5 and 300 seconds in "${entry.test_name}"`
);

assert.ok(
typeof entry.run === 'string' && entry.run.trim().length > 0,
`${label}[${i}]: "run" must be a non-empty string in "${entry.test_name}"`
);

assert.ok(
['exact', 'included', 'regex', 'not included'].includes(entry.comparison),
`${label}[${i}]: "comparison" must be one of: exact, included, regex, "not included" in "${entry.test_name}"`
);
const autograderWorkflowsDir = path.join(learningRoom, '.github/workflows');

const day1AutograderWorkflows = [
{ challenge: 2, file: 'autograder-issue-filed.yml' },
{ challenge: 5, file: 'autograder-branch-commit.yml' },
{ challenge: 6, file: 'autograder-pr-link.yml' },
{ challenge: 7, file: 'autograder-conflicts.yml' },
];

const day2AutograderWorkflows = [
{ challenge: 10, file: 'autograder-local-commit.yml' },
{ challenge: 14, file: 'autograder-template.yml' },
{ challenge: 16, file: 'autograder-capstone.yml' },
];

test('Day 1 autograder workflows exist for challenges 2, 5, 6, and 7', () => {
day1AutograderWorkflows.forEach(({ challenge, file }) => {
const wfPath = path.join(autograderWorkflowsDir, file);
assertFileExists(wfPath, `Day 1 autograder workflow for challenge ${challenge}: ${file}`);
});
}

test('autograding-day1.json is valid and complete', () => {
validateAutogradingJson(autogradingDay1Path, 'autograding-day1.json', 3);
});

test('autograding-day2.json is valid and complete', () => {
validateAutogradingJson(autogradingDay2Path, 'autograding-day2.json', 3);
});

test('autograding test names are unique within each file', () => {
[autogradingDay1Path, autogradingDay2Path].forEach(filePath => {
const tests = readJson(filePath);
const names = tests.map(t => t.test_name);
const unique = new Set(names);
assert.equal(
unique.size,
names.length,
`Duplicate test names in ${path.basename(filePath)}: ${names.filter((n, i) => names.indexOf(n) !== i).join(', ')}`
);
test('Day 2 autograder workflows exist for challenges 10, 14, and 16', () => {
day2AutograderWorkflows.forEach(({ challenge, file }) => {
const wfPath = path.join(autograderWorkflowsDir, file);
assertFileExists(wfPath, `Day 2 autograder workflow for challenge ${challenge}: ${file}`);
});
});

test('autograding day 1 covers challenges 4 5 6 and 7 at minimum', () => {
const tests = readJson(autogradingDay1Path);
const names = tests.map(t => (t.test_name || '').toLowerCase());
const required = [
['challenge 4', 'challenge 5', 'commit'], // branch or commit evidence
['challenge 5', 'commit', 'branch'],
['challenge 6', 'closes', 'issue link', 'pr'],
['challenge 7', 'conflict marker', 'conflict'],
];

required.forEach(variants => {
const found = names.some(n => variants.some(v => n.includes(v)));
assert.ok(
found,
`autograding-day1.json should cover one of: ${variants.join(' / ')}`
test('each autograder workflow posts a marker-based comment naming its challenge', () => {
[...day1AutograderWorkflows, ...day2AutograderWorkflows].forEach(({ challenge, file }) => {
const content = readText(path.join(autograderWorkflowsDir, file));
const markerPattern = new RegExp(`## Challenge ${challenge}:`);
assert.match(
content,
markerPattern,
`${file}: must post a comment with marker "## Challenge ${challenge}:" so the watchdog and update-in-place logic can identify it`
);
});
});

test('autograding day 2 covers challenges 10 through 16 at minimum', () => {
const tests = readJson(autogradingDay2Path);
const names = tests.map(t => (t.test_name || '').toLowerCase());
const required = [
['challenge 10', 'local commit', 'go local'],
['challenge 14', 'template', 'yaml'],
['challenge 16', 'capstone', 'agent'],
];
test('autograder watchdog listens for every primary autograder workflow', () => {
const watchdogPath = path.join(autograderWorkflowsDir, 'autograder-watchdog.yml');
assertFileExists(watchdogPath, 'autograder-watchdog.yml');
const content = readText(watchdogPath);

required.forEach(variants => {
const found = names.some(n => variants.some(v => n.includes(v)));
assert.ok(
found,
`autograding-day2.json should cover one of: ${variants.join(' / ')}`
[...day1AutograderWorkflows, ...day2AutograderWorkflows].forEach(({ challenge }) => {
const markerPattern = new RegExp(`## Challenge ${challenge}:`);
assert.match(
content,
markerPattern,
`autograder-watchdog.yml: must reference the "## Challenge ${challenge}:" marker so the fallback notice can detect missing comments`
);
});
});
Expand Down Expand Up @@ -407,8 +362,12 @@ test('learning-room template contains all files GitHub Classroom will copy to st
'.github/workflows/content-validation.yml',
'.github/workflows/autograder-capstone.yml',
'.github/workflows/autograder-conflicts.yml',
'.github/workflows/autograder-issue-filed.yml',
'.github/workflows/autograder-branch-commit.yml',
'.github/workflows/autograder-pr-link.yml',
'.github/workflows/autograder-local-commit.yml',
'.github/workflows/autograder-template.yml',
'.github/workflows/autograder-watchdog.yml',
'.github/scripts/challenge-progression.js',
'.github/scripts/validate-pr.js',
'.github/scripts/validation-report.js',
Expand Down
11 changes: 7 additions & 4 deletions GO-LIVE-QA-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,25 @@ Do not mark a cohort ready until all required items in this section are complete
- [ ] RSS feed validation passes for the current audio state.
- [ ] Git diff whitespace check has no actual whitespace or conflict-marker errors.
- [ ] Registration deployment gate completed (issue form template, workflow enablement, required labels, and optional classroom automation settings).
- [ ] Registration comment flow is assignment-link based (no org invite dependency).
- [ ] Support Hub is provisioned and publicly accessible at `Community-Access/support`.
- [ ] Registration confirmation and help pathways route support requests to Support Hub issues/discussions.
- [ ] Registration issue form template and labels are configured (`workshop-registration.yml`, `registration`, `duplicate`, `waitlist`).
- [ ] Learning Room source has been synced to `Community-Access/learning-room-template` and merged to `main` (or validated as no-change).
- [ ] Template smoke validation from `Community-Access/learning-room-template` succeeded before assignment publishing.
- [ ] Template freshness proof confirms smoke repo content matches latest merged template sync changes.
- [ ] Smoke repo confirms all required workflow files are present (PR validation, content validation, progression, skills progression, and all autograders).
- [ ] Smoke repo confirms all required workflow files are present (PR validation, content validation, progression, skills progression, and all autograder workflows: `autograder-issue-filed.yml`, `autograder-branch-commit.yml`, `autograder-pr-link.yml`, `autograder-conflicts.yml`, `autograder-local-commit.yml`, `autograder-template.yml`, `autograder-capstone.yml`, `autograder-watchdog.yml`).
- [ ] Day 1 Classroom assignment has been created from the current Learning Room template.
- [ ] Day 2 Classroom assignment has been created from the current Learning Room template.
- [ ] A test student account accepted the Day 1 invite and received a private repository.
- [ ] A test student account accepted the Day 2 invite and received a private repository.
- [ ] A test student can reply `ack` in enrollment issue and retain expected workflow state.
- [ ] A test student can reply `day1-complete` and receive Day 2 release comment.
- [ ] Challenge 1 can be seeded and completed.
- [ ] Challenge 10 can be seeded and completed.
- [ ] Aria posts PR feedback on a test pull request.
- [ ] Student Progression Bot creates the next challenge when a challenge issue is closed.
- [ ] Autograding runs and reports results in GitHub Classroom.
- [ ] Autograder workflows run inside the student repo and post pass/fail comments on the relevant issues and PRs. (Classroom UI test cases are intentionally not configured -- see [admin/classroom/autograding-setup.md](admin/classroom/autograding-setup.md).)
- [ ] Peer simulation artifacts can be seeded and used for review practice.
- [ ] Human testers completed the Day 1, Day 2, bonus, accessibility, and content-review passes below.
- [ ] Challenge tracking log includes explicit status and evidence for Challenges 1-16 and Bonus A-E.
Expand Down Expand Up @@ -155,7 +158,7 @@ Use a real GitHub Classroom with disposable test accounts. Do not use a facilita
- [ ] Create the Day 1 assignment using [classroom/assignment-day1-you-belong-here.md](classroom/assignment-day1-you-belong-here.md).
- [ ] Use private individual repositories.
- [ ] Enable feedback pull requests.
- [ ] Add every Day 1 autograding test from [classroom/autograding-day1.json](classroom/autograding-day1.json).
- [ ] Leave the Day 1 assignment's autograding tests area empty. Confirm autograder workflows are present in the template repo instead (see [admin/classroom/autograding-setup.md](admin/classroom/autograding-setup.md)).
- [ ] Save the Day 1 invite link.
- [ ] Accept the invite with a test student account.
- [ ] Confirm the student repository appears in the Classroom dashboard.
Expand All @@ -174,7 +177,7 @@ scripts/classroom/Seed-LearningRoomChallenge.ps1 -Repository Community-Access-Cl
- [ ] Create the Day 2 assignment using [classroom/assignment-day2-you-can-build-this.md](classroom/assignment-day2-you-can-build-this.md).
- [ ] Use private individual repositories.
- [ ] Enable feedback pull requests.
- [ ] Add every Day 2 autograding test from [classroom/autograding-day2.json](classroom/autograding-day2.json).
- [ ] Leave the Day 2 assignment's autograding tests area empty. Confirm autograder workflows are present in the template repo instead (see [admin/classroom/autograding-setup.md](admin/classroom/autograding-setup.md)).
- [ ] Save the Day 2 invite link.
- [ ] Accept the invite with the test student account.
- [ ] Confirm the Day 2 repository appears in the Classroom dashboard.
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,6 @@ Every chapter has an "If You Get Stuck" section. Every challenge has a [referenc
│ ├── README.md -- Workshop Deployment Guide (unified setup for new cohorts)
│ ├── assignment-day1-you-belong-here.md
│ ├── assignment-day2-you-can-build-this.md
│ ├── autograding-day1.json
│ ├── autograding-day2.json
│ ├── roster-template.csv
│ ├── grading-guide.md
│ └── teardown-checklist.md
Expand Down
Loading
Loading