馃 distributed transcription service
thistle.dunkirk.sh
1import { afterEach, beforeEach, expect, test } from "bun:test";
2import { unlinkSync } from "node:fs";
3import {
4 createClass,
5 createMeetingTime,
6 enrollUserInClass,
7 getClassById,
8 getClassesForUser,
9 getMeetingTimesForClass,
10 getTranscriptionsForClass,
11 isUserEnrolledInClass,
12} from "./classes";
13import { Database } from "bun:sqlite";
14
15const TEST_DB = "test-classes.db";
16let db: Database;
17
18beforeEach(() => {
19 db = new Database(TEST_DB);
20
21 // Create minimal schema for testing
22 db.run(`
23 CREATE TABLE users (
24 id INTEGER PRIMARY KEY AUTOINCREMENT,
25 email TEXT UNIQUE NOT NULL,
26 password_hash TEXT,
27 role TEXT NOT NULL DEFAULT 'user',
28 created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
29 );
30
31 CREATE TABLE classes (
32 id TEXT PRIMARY KEY,
33 course_code TEXT NOT NULL,
34 name TEXT NOT NULL,
35 professor TEXT NOT NULL,
36 semester TEXT NOT NULL,
37 year INTEGER NOT NULL,
38 archived BOOLEAN DEFAULT 0,
39 created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
40 );
41
42 CREATE TABLE class_members (
43 class_id TEXT NOT NULL,
44 user_id INTEGER NOT NULL,
45 enrolled_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
46 PRIMARY KEY (class_id, user_id),
47 FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE,
48 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
49 );
50
51 CREATE TABLE meeting_times (
52 id TEXT PRIMARY KEY,
53 class_id TEXT NOT NULL,
54 label TEXT NOT NULL,
55 created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
56 FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE
57 );
58
59 CREATE TABLE transcriptions (
60 id TEXT PRIMARY KEY,
61 user_id INTEGER NOT NULL,
62 class_id TEXT,
63 meeting_time_id TEXT,
64 filename TEXT NOT NULL,
65 original_filename TEXT NOT NULL,
66 status TEXT NOT NULL DEFAULT 'pending',
67 progress INTEGER NOT NULL DEFAULT 0,
68 created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
69 updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
70 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
71 FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE CASCADE,
72 FOREIGN KEY (meeting_time_id) REFERENCES meeting_times(id) ON DELETE SET NULL
73 );
74 `);
75});
76
77afterEach(() => {
78 db.close();
79 try {
80 unlinkSync(TEST_DB);
81 } catch {
82 // File may not exist
83 }
84});
85
86test("creates a class with all required fields", () => {
87 const cls = createClass({
88 course_code: "CS 101",
89 name: "Intro to CS",
90 professor: "Dr. Smith",
91 semester: "Fall",
92 year: 2024,
93 });
94
95 expect(cls.id).toBeTruthy();
96 expect(cls.course_code).toBe("CS 101");
97 expect(cls.name).toBe("Intro to CS");
98 expect(cls.professor).toBe("Dr. Smith");
99 expect(cls.semester).toBe("Fall");
100 expect(cls.year).toBe(2024);
101 expect(cls.archived).toBe(false);
102});
103
104test("enrolls user in class", () => {
105 // Create user
106 db.run("INSERT INTO users (email, password_hash) VALUES (?, ?)", [
107 "test@example.com",
108 "hash",
109 ]);
110 const userId = db
111 .query<{ id: number }, []>("SELECT last_insert_rowid() as id")
112 .get()?.id;
113
114 // Create class
115 const cls = createClass({
116 course_code: "CS 101",
117 name: "Intro to CS",
118 professor: "Dr. Smith",
119 semester: "Fall",
120 year: 2024,
121 });
122
123 // Enroll user
124 enrollUserInClass(userId!, cls.id);
125
126 // Verify enrollment
127 const isEnrolled = isUserEnrolledInClass(userId!, cls.id);
128 expect(isEnrolled).toBe(true);
129});
130
131test("gets classes for enrolled user", () => {
132 // Create user
133 db.run("INSERT INTO users (email, password_hash) VALUES (?, ?)", [
134 "test@example.com",
135 "hash",
136 ]);
137 const userId = db
138 .query<{ id: number }, []>("SELECT last_insert_rowid() as id")
139 .get()?.id;
140
141 // Create two classes
142 const cls1 = createClass({
143 course_code: "CS 101",
144 name: "Intro to CS",
145 professor: "Dr. Smith",
146 semester: "Fall",
147 year: 2024,
148 });
149
150 const cls2 = createClass({
151 course_code: "CS 102",
152 name: "Data Structures",
153 professor: "Dr. Jones",
154 semester: "Fall",
155 year: 2024,
156 });
157
158 // Enroll user in only one class
159 enrollUserInClass(userId!, cls1.id);
160
161 // Get classes for user
162 const classes = getClassesForUser(userId!, false);
163 expect(classes.length).toBe(1);
164 expect(classes[0]?.id).toBe(cls1.id);
165
166 // Admin should see all
167 const allClasses = getClassesForUser(userId!, true);
168 expect(allClasses.length).toBe(2);
169});
170
171test("creates and retrieves meeting times", () => {
172 const cls = createClass({
173 course_code: "CS 101",
174 name: "Intro to CS",
175 professor: "Dr. Smith",
176 semester: "Fall",
177 year: 2024,
178 });
179
180 const meeting1 = createMeetingTime(cls.id, "Monday Lecture");
181 const meeting2 = createMeetingTime(cls.id, "Wednesday Lab");
182
183 const meetings = getMeetingTimesForClass(cls.id);
184 expect(meetings.length).toBe(2);
185 expect(meetings[0]?.label).toBe("Monday Lecture");
186 expect(meetings[1]?.label).toBe("Wednesday Lab");
187});