Spaces:
Running
Running
Commit ·
2484080
1
Parent(s): a007884
Keep new submissions when applying historical classroom repairs
Browse files- classroom_service.py +4 -1
- 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"] = "校園謎案"
|