feat: parse and merge undergraduate exam schedules

This commit is contained in:
Codex
2026-10-14 23:35:30 +08:00
parent 17e4903399
commit ebe70e3eb2
4 changed files with 384 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
<script>var semesterDefault=523;</script><form id="stdExamTableForm" name="stdExamTableForm" action="https://eams.uestc.edu.cn/eams/stdExamTable!examTable.action" method="post" target="examTableFrame">
<input type="hidden" name="params" value="&amp;semester.id=503&amp;examType.id=1&amp;_=1791984366081">
<input type="hidden" name="examTakeId" value="">
<input type="hidden" name="semester.id" value="503">
<input type="hidden" name="examType.id" value="1">
<input type="hidden" name="examApplyType" value="">
</form><table width="100%" class="formTable">
<tbody><tr align="center" bgcolor="#C7DBFF" height="23px">
<td width="10%">课程序号</td>
<td width="18%">课程名称</td>
<td width="12%">考试日期</td>
<td width="22%">考试安排</td>
<td width="10%">考试地点</td>
<td width="6%">座位号</td>
<td width="8%">考试情况</td>
<td width="8%">考试类型</td>
<td width="15%">其它说明</td>
</tr>
<tr class="brightStyle" align="center" onmouseover="swapOverTR(this,this.className)" onmouseout="swapOutTR(this)" onclick="onRowChange(event)" height="23px">
<td>E1</td>
<td>考试课程1</td>
<td>2026-06-22</td>
<td> 第17周 星期一(2026-06-22) 14:00-16:00
</td>
<td>示例考场</td>
<td>1</td>
<td>正常</td>
<td>期末考试</td>
<td>
</td>
</tr>
<tr class="grayStyle" align="center" onmouseover="swapOverTR(this,this.className)" onmouseout="swapOutTR(this)" onclick="onRowChange(event)" height="23px">
<td>E2</td>
<td>考试课程2</td>
<td>2026-06-23</td>
<td> 第17周 星期二(2026-06-23) 09:30-11:30
</td>
<td>示例考场</td>
<td>1</td>
<td>正常</td>
<td>期末考试</td>
<td>
</td>
</tr>
<tr class="brightStyle" align="center" onmouseover="swapOverTR(this,this.className)" onmouseout="swapOutTR(this)" onclick="onRowChange(event)" height="23px">
<td>E3</td>
<td>考试课程3</td>
<td>2026-06-30</td>
<td> 第18周 星期二(2026-06-30) 14:00-16:00
</td>
<td>示例考场</td>
<td>1</td>
<td>正常</td>
<td>期末考试</td>
<td>
</td>
</tr>
<tr class="grayStyle" align="center" onmouseover="swapOverTR(this,this.className)" onmouseout="swapOutTR(this)" onclick="onRowChange(event)" height="23px">
<td>E4</td>
<td>考试课程4</td>
<td>2026-07-01</td>
<td> 第18周 星期三(2026-07-01) 09:30-11:30
</td>
<td>示例考场</td>
<td>1</td>
<td>正常</td>
<td>期末考试</td>
<td>
</td>
</tr>
</tbody></table>
@@ -0,0 +1,103 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:hepan_everyday/features/school/undergraduate/exam_adapter.dart';
import 'package:hepan_everyday/data/exam_file_import.dart';
import 'package:hepan_everyday/domain/school_identity.dart';
import 'package:hepan_everyday/domain/school_term.dart';
import 'package:hepan_everyday/domain/undergraduate_exam_query.dart';
import 'package:hepan_everyday/domain/exam_schedule.dart';
void main() {
String fixture() =>
File('test/fixtures/school/undergraduate-exams.html').readAsStringSync();
final term = SchoolTerm(503, school: SupportedSchool.uestcUndergraduate);
test('actual exam result term wins over the outer default', () {
final schedule = UndergraduateExamAdapter.extract(fixture());
expect(schedule.termId, '503');
expect(schedule.records, hasLength(4));
expect(schedule.requestedExamTypes, {1});
expect(schedule.records.first.date, '2026-06-22');
expect(schedule.records.first.time, contains('14:00-16:00'));
expect(ExamFileImport.parse(fixture()).termId, '503');
expect(ExamSchedule.fromJson(schedule.toJson()).records.first.status, '正常');
expect(
() => UndergraduateExamAdapter.extract(
fixture(),
expectedTerm: SchoolTerm(
523,
school: SupportedSchool.uestcUndergraduate,
),
),
throwsFormatException,
);
});
test(
'multi type merge preserves both exams for the same course and is complete',
() {
final finalExam = UndergraduateExamAdapter.extract(fixture());
final mid = UndergraduateExamAdapter.extract(
fixture()
.replaceAll('examType.id=1', 'examType.id=2')
.replaceAll(
'name="examType.id" value="1"',
'name="examType.id" value="2"',
)
.replaceAll('期末考试', '期中考试'),
);
final query = UndergraduateExamQuery(
term: term,
types: {UndergraduateExamType.finalExam, UndergraduateExamType.midterm},
);
expect(
UndergraduateExamAdapter.merge(query, [finalExam, mid]).records,
hasLength(8),
);
expect(
() => UndergraduateExamAdapter.merge(query, [finalExam]),
throwsFormatException,
);
expect(
() => UndergraduateExamAdapter.extract(
fixture(),
expectedType: UndergraduateExamType.midterm,
),
throwsFormatException,
);
},
);
test('only recognized explicit empty exam tables are empty success', () {
final empty =
'<form id="stdExamTableForm"><input name="semester.id" value="503"><input name="examType.id" value="1"></form><table><tr>' +
[
'课程序号',
'课程名称',
'考试日期',
'考试安排',
'考试地点',
'座位号',
'考试情况',
'考试类型',
'其它说明',
].map((s) => '<td>$s</td>').join() +
'</tr><tr><td colspan="9">暂无考试安排</td></tr></table>';
expect(UndergraduateExamAdapter.extract(empty).records, isEmpty);
expect(
() => UndergraduateExamAdapter.extract(empty.replaceAll('暂无考试安排', '')),
throwsFormatException,
);
expect(
() => UndergraduateExamAdapter.extract('<html>登录</html>'),
throwsFormatException,
);
});
final original = Platform.environment['UESTC_UNDERGRADUATE_EXAM_FIXTURE'];
if (original != null)
test('actual supplied exam export belongs to 503', () {
final result = UndergraduateExamAdapter.extract(
File(original).readAsStringSync(),
);
expect(result.records, hasLength(4));
expect(result.termId, '503');
});
}