Skip to content

Commit 7e2bde6

Browse files
committed
add decoding tests for TUMonline api
1 parent 8b2b3bf commit 7e2bde6

14 files changed

Lines changed: 1283 additions & 7 deletions

test/api_test.dart

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/helpers/xml_decoder.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'dart:convert';
2+
import 'package:xml2json/xml2json.dart';
3+
4+
/// Mirrors the [RestClient] response decoder exactly:
5+
/// parses XML with [Xml2Json] and converts it to a Dart [Map] using
6+
/// [toParkerWithAttrs] with the same plural-key mappings used in production.
7+
Map<String, dynamic> xmlToJson(
8+
String xml, {
9+
Map<String, String> entries = const {},
10+
}) {
11+
final transformer = Xml2Json();
12+
transformer.parse(xml);
13+
final jsonString = transformer.toParkerWithAttrs(entries: entries);
14+
return jsonDecode(jsonString) as Map<String, dynamic>;
15+
}
16+
17+
/// The exact plural-key mappings configured in [RestClient].
18+
const Map<String, String> tumOnlineEntries = {
19+
"row": "rowset",
20+
"event": "events",
21+
"studium": "studien",
22+
"raum": "raeume",
23+
"gruppe": "gruppen",
24+
"nebenstelle": "telefon_nebenstellen",
25+
"card": "cards",
26+
};
27+
28+
/// Convenience wrapper: decode TUM Online XML using all production mappings.
29+
Map<String, dynamic> tumXmlToJson(String xml) =>
30+
xmlToJson(xml, entries: tumOnlineEntries);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import 'package:campus_flutter/studiesComponent/model/average_grade.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
import '../helpers/xml_decoder.dart';
5+
6+
void main() {
7+
group('AverageGrades XML decoding', () {
8+
test('decodes average grades using the "studien" plural key', () {
9+
// The xml2json mapping converts each <studium> element to a "studien" list
10+
const xml = '''
11+
<rowset>
12+
<studium>
13+
<studidf>IN20200001</studidf>
14+
<studbez>Informatik (B.Sc.)</studbez>
15+
<avg_grade_weighted_by_credits>1,7</avg_grade_weighted_by_credits>
16+
</studium>
17+
<studium>
18+
<studidf>MA20200002</studidf>
19+
<studbez>Mathematik (B.Sc.)</studbez>
20+
<avg_grade_weighted_by_credits>2,3</avg_grade_weighted_by_credits>
21+
</studium>
22+
</rowset>
23+
''';
24+
final json = tumXmlToJson(xml);
25+
final averageGrades = AverageGrades.fromJson(json);
26+
27+
expect(averageGrades.averageGrades.length, 2);
28+
29+
final first = averageGrades.averageGrades.first;
30+
expect(first.id, 'IN20200001');
31+
expect(first.studyDesignation, 'Informatik (B.Sc.)');
32+
// StringParser.stringToDouble replaces commas with dots
33+
expect(first.averageGrade, closeTo(1.7, 0.001));
34+
35+
final second = averageGrades.averageGrades[1];
36+
expect(second.id, 'MA20200002');
37+
expect(second.averageGrade, closeTo(2.3, 0.001));
38+
});
39+
40+
test('handles null average grade', () {
41+
const xml = '''
42+
<rowset>
43+
<studium>
44+
<studidf>IN20200001</studidf>
45+
<studbez>Informatik (B.Sc.)</studbez>
46+
</studium>
47+
</rowset>
48+
''';
49+
final json = tumXmlToJson(xml);
50+
final averageGrades = AverageGrades.fromJson(json);
51+
52+
expect(averageGrades.averageGrades.first.averageGrade, isNull);
53+
});
54+
55+
test('returns empty list when rowset has no studium elements', () {
56+
const xml = '<rowset/>';
57+
final json = tumXmlToJson(xml);
58+
final averageGrades = AverageGrades.fromJson(json);
59+
60+
expect(averageGrades.averageGrades, isEmpty);
61+
});
62+
63+
test('decodes comma-formatted grade to double', () {
64+
const xml = '''
65+
<rowset>
66+
<studium>
67+
<studidf>ID001</studidf>
68+
<studbez>Study</studbez>
69+
<avg_grade_weighted_by_credits>1,3</avg_grade_weighted_by_credits>
70+
</studium>
71+
</rowset>
72+
''';
73+
final json = tumXmlToJson(xml);
74+
final grade = AverageGrades.fromJson(json).averageGrades.first;
75+
76+
expect(grade.averageGrade, closeTo(1.3, 0.001));
77+
});
78+
});
79+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:campus_flutter/calendarComponent/model/calendar_editing.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
import '../helpers/xml_decoder.dart';
5+
6+
void main() {
7+
group('CalendarCreationConfirmationData XML decoding', () {
8+
test('decodes a creation confirmation response', () {
9+
const xml = '''
10+
<rowset>
11+
<termin>
12+
<nr>987654</nr>
13+
</termin>
14+
</rowset>
15+
''';
16+
final json = tumXmlToJson(xml);
17+
final data = CalendarCreationConfirmationData.fromJson(json);
18+
19+
expect(data.calendarCreationConfirmation.eventId, '987654');
20+
});
21+
});
22+
23+
group('CalendarDeletionConfirmationData XML decoding', () {
24+
test('decodes a deletion confirmation response', () {
25+
const xml = '''
26+
<rowset>
27+
<termin>
28+
<delete>OK</delete>
29+
</termin>
30+
</rowset>
31+
''';
32+
final json = tumXmlToJson(xml);
33+
final data = CalendarDeletionConfirmationData.fromJson(json);
34+
35+
expect(data.calendarDeletionConfirmation.status, 'OK');
36+
});
37+
});
38+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import 'package:campus_flutter/calendarComponent/model/calendar_event.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
import '../helpers/xml_decoder.dart';
5+
6+
void main() {
7+
group('CalendarEvents XML decoding', () {
8+
test('decodes multiple events from XML', () {
9+
const xml = '''
10+
<rowset>
11+
<event>
12+
<nr>12345</nr>
13+
<status>CONFIRMED</status>
14+
<url>https://campus.tum.de/tumonline/wbLv.wbShowLVDetail?pStpSpNr=123456789&amp;LvNr=950123456</url>
15+
<title>Introduction to Computer Science</title>
16+
<description>Lecture on CS fundamentals</description>
17+
<dtstart>2024-04-15 10:00:00</dtstart>
18+
<dtend>2024-04-15 12:00:00</dtend>
19+
<location>MI 00.08.038, Garching</location>
20+
</event>
21+
<event>
22+
<nr>67890</nr>
23+
<status>CONFIRMED</status>
24+
<title>Advanced Algorithms</title>
25+
<dtstart>2024-04-16 14:00:00</dtstart>
26+
<dtend>2024-04-16 16:00:00</dtend>
27+
<location>MI 02.08.011</location>
28+
<location>MI 02.08.012</location>
29+
</event>
30+
</rowset>
31+
''';
32+
final json = tumXmlToJson(xml);
33+
// After toParkerWithAttrs the "event" key is pluralised to "events"
34+
final events = CalendarEvents.fromJson(json);
35+
36+
expect(events.events.length, 2);
37+
38+
final first = events.events.first;
39+
expect(first.id, '12345');
40+
expect(first.status, 'CONFIRMED');
41+
expect(first.title, 'Introduction to Computer Science');
42+
expect(first.description, 'Lecture on CS fundamentals');
43+
expect(first.startDate, DateTime.parse('2024-04-15 10:00:00'));
44+
expect(first.endDate, DateTime.parse('2024-04-15 12:00:00'));
45+
expect(first.locations, ['MI 00.08.038, Garching']);
46+
expect(first.lvNr, '950123456');
47+
expect(first.isCanceled, isFalse);
48+
49+
final second = events.events[1];
50+
expect(second.id, '67890');
51+
expect(second.locations.length, 2);
52+
expect(second.locations, containsAll(['MI 02.08.011', 'MI 02.08.012']));
53+
});
54+
55+
test('decodes a single event (location as single value, not list)', () {
56+
const xml = '''
57+
<rowset>
58+
<event>
59+
<nr>11111</nr>
60+
<status>CONFIRMED</status>
61+
<title>Single Location Event</title>
62+
<dtstart>2024-05-01 08:00:00</dtstart>
63+
<dtend>2024-05-01 10:00:00</dtend>
64+
<location>MI 01.06.020</location>
65+
</event>
66+
</rowset>
67+
''';
68+
final json = tumXmlToJson(xml);
69+
final events = CalendarEvents.fromJson(json);
70+
71+
expect(events.events.length, 1);
72+
// readListValue should wrap a single string in a list
73+
expect(events.events.first.locations, ['MI 01.06.020']);
74+
});
75+
76+
test('returns empty events list when rowset has no events', () {
77+
const xml = '<rowset/>';
78+
final json = tumXmlToJson(xml);
79+
final events = CalendarEvents.fromJson(json);
80+
81+
expect(events.events, isEmpty);
82+
});
83+
84+
test('decodes a canceled event', () {
85+
const xml = '''
86+
<rowset>
87+
<event>
88+
<nr>99999</nr>
89+
<status>CANCEL</status>
90+
<title>Canceled Lecture</title>
91+
<dtstart>2024-06-01 10:00:00</dtstart>
92+
<dtend>2024-06-01 12:00:00</dtend>
93+
</event>
94+
</rowset>
95+
''';
96+
final json = tumXmlToJson(xml);
97+
final events = CalendarEvents.fromJson(json);
98+
99+
expect(events.events.first.isCanceled, isTrue);
100+
// No location in XML → readListValue returns []
101+
expect(events.events.first.locations, isEmpty);
102+
});
103+
104+
test('duration is calculated correctly', () {
105+
const xml = '''
106+
<rowset>
107+
<event>
108+
<nr>55555</nr>
109+
<status>CONFIRMED</status>
110+
<title>Two Hour Lecture</title>
111+
<dtstart>2024-04-15 10:00:00</dtstart>
112+
<dtend>2024-04-15 12:00:00</dtend>
113+
</event>
114+
</rowset>
115+
''';
116+
final json = tumXmlToJson(xml);
117+
final events = CalendarEvents.fromJson(json);
118+
expect(events.events.first.duration, const Duration(hours: 2));
119+
});
120+
});
121+
}

test/tumOnlineApi/grade_test.dart

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import 'package:campus_flutter/studiesComponent/model/grade.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
import '../helpers/xml_decoder.dart';
5+
6+
void main() {
7+
group('Grades XML decoding', () {
8+
test('decodes a list of grades from XML', () {
9+
const xml = '''
10+
<rowset>
11+
<row>
12+
<datum>2024-01-15 00:00:00</datum>
13+
<lv_nummer>0000001234</lv_nummer>
14+
<lv_semester>24S</lv_semester>
15+
<lv_titel>Introduction to Computer Science</lv_titel>
16+
<pruefer_nachname>Müller</pruefer_nachname>
17+
<uninotenamekurz>1,3</uninotenamekurz>
18+
<exam_typ_name>Modulprüfung</exam_typ_name>
19+
<modus>Schriftlich</modus>
20+
<studienidentifikator>IN20200001</studienidentifikator>
21+
<studienbezeichnung>Informatik (B.Sc.)</studienbezeichnung>
22+
<st_studium_nr>100001</st_studium_nr>
23+
<abschluss_name>Bachelor of Science</abschluss_name>
24+
<abschluss_name_kurz>B.Sc.</abschluss_name_kurz>
25+
</row>
26+
<row>
27+
<datum>2024-02-20 00:00:00</datum>
28+
<lv_nummer>0000005678</lv_nummer>
29+
<lv_semester>24S</lv_semester>
30+
<lv_titel>Linear Algebra</lv_titel>
31+
<pruefer_nachname>Schmidt</pruefer_nachname>
32+
<uninotenamekurz>2,0</uninotenamekurz>
33+
<exam_typ_name>Modulprüfung</exam_typ_name>
34+
<modus>Schriftlich und Mündlich</modus>
35+
<studienidentifikator>IN20200001</studienidentifikator>
36+
<studienbezeichnung>Informatik (B.Sc.)</studienbezeichnung>
37+
<st_studium_nr>100001</st_studium_nr>
38+
</row>
39+
</rowset>
40+
''';
41+
final json = tumXmlToJson(xml);
42+
final grades = Grades.fromJson(json);
43+
44+
expect(grades.personalGrades.length, 2);
45+
46+
final first = grades.personalGrades.first;
47+
expect(first.lvNumber, '0000001234');
48+
expect(first.semester, '24S');
49+
expect(first.title, 'Introduction to Computer Science');
50+
expect(first.examiner, 'Müller');
51+
expect(first.grade, '1,3');
52+
expect(first.examType, 'Modulprüfung');
53+
expect(first.mode, 'Schriftlich');
54+
expect(first.studyID, 'IN20200001');
55+
expect(first.studyDesignation, 'Informatik (B.Sc.)');
56+
expect(first.studyNumber, '100001');
57+
expect(first.degree, 'Bachelor of Science');
58+
expect(first.degreeShort, 'B.Sc.');
59+
expect(first.date, DateTime.parse('2024-01-15 00:00:00'));
60+
61+
final second = grades.personalGrades[1];
62+
expect(second.lvNumber, '0000005678');
63+
expect(second.degree, isNull);
64+
expect(second.degreeShort, isNull);
65+
});
66+
67+
test('returns empty grades list when rowset has no rows', () {
68+
const xml = '<rowset/>';
69+
final json = tumXmlToJson(xml);
70+
final grades = Grades.fromJson(json);
71+
72+
expect(grades.personalGrades, isEmpty);
73+
});
74+
75+
test('grade id combines date and lvNumber', () {
76+
const xml = '''
77+
<rowset>
78+
<row>
79+
<datum>2024-01-15 00:00:00</datum>
80+
<lv_nummer>0000001234</lv_nummer>
81+
<lv_semester>24S</lv_semester>
82+
<lv_titel>CS</lv_titel>
83+
<pruefer_nachname>Müller</pruefer_nachname>
84+
<uninotenamekurz>1,3</uninotenamekurz>
85+
<exam_typ_name>Prüfung</exam_typ_name>
86+
<modus>Schriftlich</modus>
87+
<studienidentifikator>IN001</studienidentifikator>
88+
<studienbezeichnung>Informatik</studienbezeichnung>
89+
<st_studium_nr>100</st_studium_nr>
90+
</row>
91+
</rowset>
92+
''';
93+
final json = tumXmlToJson(xml);
94+
final grade = Grades.fromJson(json).personalGrades.first;
95+
96+
expect(
97+
grade.id,
98+
'${grade.date?.toIso8601String()}-${grade.lvNumber}',
99+
);
100+
});
101+
});
102+
}

0 commit comments

Comments
 (0)