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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# **.mjs
48 changes: 48 additions & 0 deletions __tests__/csvAlgorithm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
matchCanonicalTrack,
sortTracks,
} from '@utils/csv-ingestion/csvAlgorithm';

describe('csvAlgorithm track matching', () => {
it('matches tracks case-insensitively to canonical names', () => {
expect(matchCanonicalTrack('best hardware hack')).toBe(
'Best Hardware Hack'
);
expect(matchCanonicalTrack('Best hardware hack')).toBe(
'Best Hardware Hack'
);
});

it('does not attempt to correct spelling', () => {
expect(matchCanonicalTrack('Best Hardwre Hack')).toBeNull();
expect(matchCanonicalTrack('Best Assistive Technlogy')).toBeNull();
});

it('ingests all opt-in tracks and does not cap length', () => {
const tracks = sortTracks(
'best hardware hack',
'',
'',
'Best Use of Gemini API; Best Use of MongoDB Atlas, Best Use of Vectara | Best Use of Auth0'
);

expect(tracks).toEqual([
'Best Hardware Hack',
'Best Use of Gemini API',
'Best Use of MongoDB Atlas',
'Best Use of Vectara',
'Best Use of Auth0',
]);
});

it('filters out excluded tracks', () => {
const tracks = sortTracks(
'Best Hack for Social Good',
"Hacker's Choice Award",
'',
'Best Hack for Social Good, Best Hardware Hack'
);

expect(tracks).toEqual(['Best Hardware Hack']);
});
});
90 changes: 90 additions & 0 deletions __tests__/csvValidation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { validateCsvBlob } from '@utils/csv-ingestion/csvAlgorithm';

describe('csvAlgorithm validation', () => {
it("silently ignores 'N/A' without warnings", async () => {
const csv =
'Table Number,Project Status,Project Title,Track #1 (Primary Track),Track #2,Track #3,Opt-In Prizes\n' +
'12,Submitted (Gallery/Visible),Test Project,Best Beginner Hack,N/A,,\n';

const blob = new Blob([csv], { type: 'text/csv' });
const res = await validateCsvBlob(blob);

expect(res.ok).toBe(true);
expect(res.report.errorRows).toBe(0);
expect(res.report.warningRows).toBe(0);
expect(res.report.issues).toEqual([]);
});

it('treats duplicate tracks as warnings (non-blocking)', async () => {
const csv =
'Table Number,Project Status,Project Title,Track #1 (Primary Track),Track #2,Track #3,Opt-In Prizes\n' +
'87,Submitted (Gallery/Visible),PartyPal,Best UI/UX Design,Best UI/UX Design,,\n';

const blob = new Blob([csv], { type: 'text/csv' });
const res = await validateCsvBlob(blob);

expect(res.ok).toBe(true);
expect(res.report.errorRows).toBe(0);
expect(res.report.warningRows).toBe(1);
expect(res.report.issues[0].severity).toBe('warning');
expect(res.report.issues[0].duplicateTracks).toEqual(['Best UI/UX Design']);
});

it('detects duplicate teamNumbers as errors', async () => {
const csv =
'Table Number,Project Status,Project Title,Track #1 (Primary Track),Track #2,Track #3,Opt-In Prizes\n' +
'42,Submitted (Gallery/Visible),Project A,Best Hardware Hack,,,\n' +
'42,Submitted (Gallery/Visible),Project B,Best UI/UX Design,,,\n';

const blob = new Blob([csv], { type: 'text/csv' });
const res = await validateCsvBlob(blob);

// Should have 1 error issue: second row with same teamNumber is flagged
expect(res.ok).toBe(false);
expect(res.report.errorRows).toBe(1);
expect(res.report.issues.length).toBe(1);
expect(res.report.issues[0].severity).toBe('error');
expect(res.report.issues[0].duplicateTeamNumber).toBe(42);
expect(res.report.issues[0].teamNumber).toBe(42);
});

it('detects multiple duplicate teamNumbers', async () => {
const csv =
'Table Number,Project Status,Project Title,Track #1 (Primary Track),Track #2,Track #3,Opt-In Prizes\n' +
'10,Submitted (Gallery/Visible),Project A,Best Hardware Hack,,,\n' +
'10,Submitted (Gallery/Visible),Project B,Best UI/UX Design,,,\n' +
'20,Submitted (Gallery/Visible),Project C,Best Beginner Hack,,,\n' +
'20,Submitted (Gallery/Visible),Project D,Best Use of AWS,,,\n';

const blob = new Blob([csv], { type: 'text/csv' });
const res = await validateCsvBlob(blob);

expect(res.ok).toBe(false);
expect(res.report.errorRows).toBe(2);
expect(res.report.issues.length).toBe(2);

// Both should be errors with duplicateTeamNumber set
const duplicateIssues = res.report.issues.filter(
(i) => i.duplicateTeamNumber !== undefined
);
expect(duplicateIssues.length).toBe(2);
expect(duplicateIssues[0].duplicateTeamNumber).toBe(10);
expect(duplicateIssues[1].duplicateTeamNumber).toBe(20);
});

it('does not flag unique teamNumbers as duplicates', async () => {
const csv =
'Table Number,Project Status,Project Title,Track #1 (Primary Track),Track #2,Track #3,Opt-In Prizes\n' +
'10,Submitted (Gallery/Visible),Project A,Best Hardware Hack,,,\n' +
'11,Submitted (Gallery/Visible),Project B,Best UI/UX Design,,,\n' +
'12,Submitted (Gallery/Visible),Project C,Best Beginner Hack,,,\n';

const blob = new Blob([csv], { type: 'text/csv' });
const res = await validateCsvBlob(blob);

expect(res.ok).toBe(true);
expect(res.report.errorRows).toBe(0);
expect(res.report.warningRows).toBe(0);
expect(res.report.issues).toEqual([]);
});
});
88 changes: 88 additions & 0 deletions __tests__/logic/checkTeamsPopulated.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { db } from '../../jest.setup';
import checkTeamsPopulated from '@actions/logic/checkTeamsPopulated';
import * as mongoClient from '@utils/mongodb/mongoClient.mjs';

beforeEach(async () => {
await db.collection('teams').deleteMany({});
});

describe('checkTeamsPopulated', () => {
it('should return populated false and count 0 when no teams exist', async () => {
const result = await checkTeamsPopulated();
expect(result.ok).toBe(true);
expect(result.populated).toBe(false);
expect(result.count).toBe(0);
expect(result.error).toBe(null);
});

it('should return populated true and correct count when teams exist', async () => {
await db.collection('teams').insertMany(
[
{
name: 'Team 1',
teamNumber: 1,
tableNumber: 1,
tracks: ['Best Hardware Hack'],
active: true,
},
{
name: 'Team 2',
teamNumber: 2,
tableNumber: 2,
tracks: ['Data Science/Machine Learning'],
active: true,
},
{
name: 'Team 3',
teamNumber: 3,
tableNumber: 3,
tracks: ['Beginner'],
active: false,
},
],
{ bypassDocumentValidation: true }
);

const result = await checkTeamsPopulated();
expect(result.ok).toBe(true);
expect(result.populated).toBe(true);
expect(result.count).toBe(3);
expect(result.error).toBe(null);
});

it('should return populated true and count 1 when exactly one team exists', async () => {
await db.collection('teams').insertOne(
{
name: 'Solo Team',
teamNumber: 1,
tableNumber: 1,
tracks: ['Best Hardware Hack'],
active: true,
},
{ bypassDocumentValidation: true }
);

const result = await checkTeamsPopulated();
expect(result.ok).toBe(true);
expect(result.populated).toBe(true);
expect(result.count).toBe(1);
expect(result.error).toBe(null);
});

it('should handle database errors gracefully', async () => {
// Mock the getDatabase to throw an error
const mockGetDatabase = jest
.spyOn(mongoClient, 'getDatabase')
.mockRejectedValue(new Error('Database connection failed'));

const result = await checkTeamsPopulated();

expect(result.ok).toBe(false);
expect(result.populated).toBe(false);
expect(result.count).toBe(0);
expect(result.error).toBe('Database connection failed');

// Restore the mock
mockGetDatabase.mockRestore();
});
});
Loading