steveyehtw commited on
Commit
2484080
·
1 Parent(s): a007884

Keep new submissions when applying historical classroom repairs

Browse files
Files changed (2) hide show
  1. classroom_service.py +4 -1
  2. tests/test_classroom.py +26 -0
classroom_service.py CHANGED
@@ -15,6 +15,8 @@ COURSE_STORAGE_QUOTA_BYTES=max(16, int(os.getenv("CLASSROOM_COURSE_QUOTA_MB", "1
15
  MAX_VERSIONS_PER_KIND=max(2, int(os.getenv("CLASSROOM_MAX_VERSIONS_PER_KIND", "100")))
16
  CHECKPOINTS_PER_HOUR=max(1, int(os.getenv("CLASSROOM_CHECKPOINTS_PER_HOUR", "60")))
17
  SUBMISSIONS_PER_HOUR=max(1, int(os.getenv("CLASSROOM_SUBMISSIONS_PER_HOUR", "20")))
 
 
18
  VERSION_WRITE_COOLDOWN_MS=max(0, int(os.getenv("CLASSROOM_VERSION_COOLDOWN_MS", "1500")))
19
  STUDENT_WS_CONNECTIONS_PER_MINUTE=max(10, int(os.getenv("CLASSROOM_WS_CONNECTIONS_PER_MINUTE", "300")))
20
  STUDENT_WS_MESSAGES_PER_MINUTE=max(60, int(os.getenv("CLASSROOM_WS_MESSAGES_PER_MINUTE", "300")))
@@ -145,8 +147,9 @@ def init_db():
145
  if not c.execute("SELECT 1 FROM classroom_migrations WHERE migration_id=?",(migration_id,)).fetchone():
146
  for repair in REPAIRS.glob("*/*/*.py") if REPAIRS.is_dir() else []:
147
  class_code,week_id=repair.parts[-3],repair.parts[-2];student_id=repair.stem
148
- row=c.execute("SELECT draft_files,final_files,checkpoint_at,submitted_at FROM classroom_projects WHERE class_code=? AND student_id=? AND week_id=?",(class_code,student_id,week_id)).fetchone()
149
  if not row:continue
 
150
  draft=json.loads(row[0]);final=json.loads(row[1]) if row[1] else None;fixed=repair.read_text(encoding="utf-8")
151
  if row[2] is None or row[2]<int(row[3] or 0):draft["main.py"]=fixed
152
  if final is not None:final["main.py"]=fixed
 
15
  MAX_VERSIONS_PER_KIND=max(2, int(os.getenv("CLASSROOM_MAX_VERSIONS_PER_KIND", "100")))
16
  CHECKPOINTS_PER_HOUR=max(1, int(os.getenv("CLASSROOM_CHECKPOINTS_PER_HOUR", "60")))
17
  SUBMISSIONS_PER_HOUR=max(1, int(os.getenv("CLASSROOM_SUBMISSIONS_PER_HOUR", "20")))
18
+ # Bundled syntax repairs target work saved before 2026-10-04 00:00 Asia/Taipei.
19
+ LEGACY_REPAIR_CUTOFF_MS=1791043200000
20
  VERSION_WRITE_COOLDOWN_MS=max(0, int(os.getenv("CLASSROOM_VERSION_COOLDOWN_MS", "1500")))
21
  STUDENT_WS_CONNECTIONS_PER_MINUTE=max(10, int(os.getenv("CLASSROOM_WS_CONNECTIONS_PER_MINUTE", "300")))
22
  STUDENT_WS_MESSAGES_PER_MINUTE=max(60, int(os.getenv("CLASSROOM_WS_MESSAGES_PER_MINUTE", "300")))
 
147
  if not c.execute("SELECT 1 FROM classroom_migrations WHERE migration_id=?",(migration_id,)).fetchone():
148
  for repair in REPAIRS.glob("*/*/*.py") if REPAIRS.is_dir() else []:
149
  class_code,week_id=repair.parts[-3],repair.parts[-2];student_id=repair.stem
150
+ row=c.execute("SELECT draft_files,final_files,checkpoint_at,submitted_at,updated_at FROM classroom_projects WHERE class_code=? AND student_id=? AND week_id=?",(class_code,student_id,week_id)).fetchone()
151
  if not row:continue
152
+ if max(int(row[2] or 0),int(row[3] or 0),int(row[4] or 0))>=LEGACY_REPAIR_CUTOFF_MS:continue
153
  draft=json.loads(row[0]);final=json.loads(row[1]) if row[1] else None;fixed=repair.read_text(encoding="utf-8")
154
  if row[2] is None or row[2]<int(row[3] or 0):draft["main.py"]=fixed
155
  if final is not None:final["main.py"]=fixed
tests/test_classroom.py CHANGED
@@ -6,6 +6,7 @@ import unittest
6
  import zipfile
7
  from pathlib import Path
8
  from types import SimpleNamespace
 
9
 
10
  import classroom_service as classroom
11
 
@@ -91,6 +92,31 @@ class ClassroomServiceTests(unittest.TestCase):
91
  self.assertEqual(len(matching), 1)
92
  self.assertEqual(matching[0]["files"], loaded["checkpoint_files"])
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  def test_progress_picker_lists_every_checkpoint_and_submission_newest_first(self):
95
  state = classroom.empty("L6A", "student-1", "Alex", "week02")
96
  state["project_name"] = "校園謎案"
 
6
  import zipfile
7
  from pathlib import Path
8
  from types import SimpleNamespace
9
+ from unittest.mock import patch
10
 
11
  import classroom_service as classroom
12
 
 
92
  self.assertEqual(len(matching), 1)
93
  self.assertEqual(matching[0]["files"], loaded["checkpoint_files"])
94
 
95
+ def test_fresh_bootstrap_does_not_replace_new_submission_with_legacy_repair(self):
96
+ data = Path(self.temp_dir.name) / "seed"
97
+ data.mkdir()
98
+ (data / "classes.json").write_text("[]", encoding="utf-8")
99
+ versions = data / "versions"
100
+ repair = data / "repairs" / "L6A" / "week02" / "student.py"
101
+ repair.parent.mkdir(parents=True)
102
+ repair.write_text('print("old repair")\n', encoding="utf-8")
103
+ files = {"main.py": 'print("latest student story")\n'}
104
+ timestamp = classroom.LEGACY_REPAIR_CUTOFF_MS + 1
105
+ record = {"class_code": "L6A", "student_id": "student", "display_name": "Alex",
106
+ "week_id": "week02", "version_id": "teacher-submit", "kind": "submit",
107
+ "project_name": "case", "created_at": timestamp, "files": files}
108
+ version = versions / "L6A" / "student" / "week02" / "teacher-submit.json"
109
+ version.parent.mkdir(parents=True)
110
+ version.write_text(json.dumps(record), encoding="utf-8")
111
+ with patch.multiple(classroom, DATA=data, REPAIRS=data / "repairs",
112
+ VERSIONS=versions, CHECKPOINTS=data / "checkpoints",
113
+ DB=Path(self.temp_dir.name) / "fresh.sqlite3"):
114
+ classroom.init_db()
115
+ restored = classroom.load("L6A", "student", "Alex", "week02")
116
+ self.assertEqual(restored["files"], files)
117
+ self.assertEqual(restored["final_files"], files)
118
+ self.assertEqual(restored["submitted_at"], timestamp)
119
+
120
  def test_progress_picker_lists_every_checkpoint_and_submission_newest_first(self):
121
  state = classroom.empty("L6A", "student-1", "Alex", "week02")
122
  state["project_name"] = "校園謎案"