Local mock server that replicates the ETSMobileAPI for testing the ÉTSMobile Flutter app.
- Quick Start
- Schedule Editor UI
- Supported Format
- Endpoints
- Managing Courses
- Profiles
- Scenarios
- Failure Injection
- Sample Data
- Authentication
- Customizing Data
- Connecting the Flutter App
pip install -r requirements.txt
python start.pyThis launches an interactive menu to pick a student profile, an optional calendar scenario, and starts the server. The server runs at http://localhost:8080. You can access API docs at http://localhost:8080/docs.
To skip the menu, pass the configuration as flags instead:
python start.py --profile semester-off
python start.py --courses 2 --days 1,3,5 --time morning
python start.py --scenario semaine-relache --semester-week 3python start.py --help lists every profile, scenario and day code.
A visual weekly-schedule editor is served at http://localhost:8080/editor
(the root / redirects there). It shows the active session's courses in a week
grid and lets you move, resize, add and delete them. Edits are written back to
the mock, so the API endpoints serve the edited schedule.
Nothing extra is needed to run it. Start the server and open the page:
python start.pyThe toolbar switches between two scopes:
- All weeks edits the weekly slot, so the change applies to every occurrence of that block.
- This occurrence edits only the displayed week.
An occurrence with a week-specific change is marked as modified and can only be dragged in This occurrence; reset it to put it back on the series slot.
Run the editor's tests with python -m pytest tests/.
The editor's front-end assets are already built and committed, so running the mock only needs Python. To rebuild:
cd web
npm install
npm run buildThe server supports both JSON (default) and XML responses:
# JSON (default)
curl http://localhost:8080/api/Etudiant/infoEtudiant
# XML
curl -H "Accept: application/xml" http://localhost:8080/api/Etudiant/infoEtudiantIn Windows PowerShell 5.1, use curl.exe.
All endpoints are GET /api/Etudiant/...:
| Endpoint | Parameters | Description |
|---|---|---|
helloWorld |
- | Returns "Hello World" |
echo |
chaine |
Echoes back the string |
infoEtudiant |
- | Student profile |
listeCours |
- | All courses |
listeCoursIntervalleSessions |
sessionDebut, sessionFin |
Courses in session range |
listeProgrammes |
- | Student programs |
listeSessions |
- | All sessions |
lireEvaluationCours |
session |
Course evaluations for a session |
lireHoraireDesSeances |
session, coursGroupe?, dateDebut?, dateFin? |
Course activity sessions |
listeHoraireEtProf |
session |
Schedule activities + professors |
listeElementsEvaluation |
session, sigle, groupe |
Grades and class statistics |
lireHoraire |
session, prefixe |
Courses by prefix with professors |
listeHoraireExamensFin |
session |
Final exam schedules |
lireJoursRemplaces |
session |
Replaced days |
listeCoequipiers |
session, sigle, groupe, nomElementEval |
Teammates |
Courses are defined in seed/courses.json. Data is computed at startup.
# Interactive CLI to add/remove courses
python manage_seed.pyYou can also use the programmatic API:
from manage_seed import add_course_to_seed, remove_course_from_seed
add_course_to_seed(
session="H2026",
sigle="LOG999",
titre="My New Course",
schedule={"jour": "2", "journee": "Mardi", "heureDebut": "09:00", "heureFin": "12:30",
"codeActivite": "C", "nomActivite": "Activité de cours"},
)Pick a profile from the python start.py menu, or name it directly:
python start.py --profile semester-off| Profile | Description |
|---|---|
normal (default) |
4 generated courses + labs, Mon-Fri mornings/afternoons |
semester-off |
No courses in active session |
internship-only |
Coop program, no courses |
internship-courses |
Coop program + 2 generated evening courses |
new-student |
Brand-new student, no sessions or courses |
generated-light |
2 courses + labs, Mon-Fri mornings |
generated-busy |
5 courses + labs, Mon-Fri |
generated-evening |
3 courses + labs, Mon-Fri evenings |
Profiles are defined in seed/profiles.json. Add a new profile by adding a JSON object.
The interactive menu (python start.py) offers a "Custom" option that prompts for course count, schedule days, and time preference.
You can set the same values as flags, on top of any profile:
| Flag | Description |
|---|---|
--courses N |
Number of courses (1-5) |
--days 1,3,5 |
Comma-separated day codes (1=Mon, 6=Sat) |
--time morning |
morning, afternoon, evening (comma-separated for multiple) |
python start.py --courses 2 --days 1,3,5
python start.py --profile generated-busy --time eveningInvalid values are rejected before the server starts.
By default, the mock uses the real session calendar from seed/sessions.json, so running the server near the end of a semester leaves few upcoming activities, exams in the past, and most grades already published. To simulate being at a specific week of the active session, set:
python start.py --semester-week 3This shifts the active session's dateDebut (and all other date fields) so that today falls at the chosen week. The next session is shifted by the same offset to preserve the gap between them.
Scenarios apply calendar modifications to the active session (skipped days, replaced days). Select a scenario from the start.py menu, or name it directly:
python start.py --scenario semaine-relache| Scenario | Description |
|---|---|
none (default) |
No calendar modifications |
friday-off |
Next Friday has no courses |
semaine-relache |
Next full week off |
monday-holiday |
Next Monday is a holiday (replaced by Tuesday) |
long-weekend |
Next Friday + Monday off |
Scenarios are defined declaratively in seed/scenarios.json.
The mock can simulate broken-server conditions. Set them at startup with flags, or change them on a running server through /admin/failures.
| Flag | Effect |
|---|---|
--failures PRESET |
Apply a named preset (list) |
--latency MS |
Add latency before every API response. Fixed (500) or a range (100-800) |
--error-rate R |
Probability (0.0-1.0) that any API call returns a 500 |
--fail ENDPOINT |
Endpoint that always returns 503. Repeatable, * for all |
--timeout ENDPOINT |
Endpoint that hangs the request. Repeatable, * for all |
--timeout-duration S |
How long a hanging endpoint sleeps before a 504 (default 60) |
--malformed |
Truncate every successful 2xx response body in half |
--auth |
Return 401 on API requests without an Authorization header |
python start.py --failures flaky
python start.py --latency 200-600 --error-rate 0.1
python start.py --profile semester-off --authA preset can be adjusted by adding flags after it. --failures flaky --error-rate 0.9 keeps the preset's latency and replaces its error rate.
--malformed and --auth each have a --no- form.
# View current config
curl http://localhost:8080/admin/failures
# Patch any subset of fields
curl -X PATCH http://localhost:8080/admin/failures \
-H 'Content-Type: application/json' \
-d '{"latencyMs": "100-500", "errorRate": 0.2, "failEndpoints": ["listeCoequipiers"]}'
# Reset everything to defaults
curl -X DELETE http://localhost:8080/admin/failuresPATCH body fields: latencyMs (int or "min-max" string), errorRate (0.0-1.0), failEndpoints (list), timeoutEndpoints (list), timeoutDurationS (float), malformed (bool), authRequired (bool). All optional.
For day-to-day use you usually don't want to hand-write JSON. The manage_failures.py CLI applies named presets defined in seed/failure_presets.json:
python manage_failures.py list # show available presets
python manage_failures.py status # show current config
python manage_failures.py flaky # apply a preset
python manage_failures.py reset # clear everything (alias: off)
python manage_failures.py custom --error-rate 0.5 --latency 100-500 --fail listeCoequipiers| Preset | Effect |
|---|---|
flaky |
100-800ms latency, 30% random 500s |
slow |
2-5s latency on every API call |
outage |
Every API endpoint returns 503 |
partial-outage |
Grades and teammates endpoints down |
auth |
Require an Authorization header |
corrupt |
Truncate every successful response body |
timeout-grades |
Grade endpoints hang for 30s before returning 504 |
chaos |
Latency + errors + corrupted bodies all at once |
Add new presets by editing seed/failure_presets.json.
The mock server returns data for a fictional ÉTS software engineering student with:
- Sessions: H2024, É2024, A2024, H2025, É2025, A2025, H2026
- 15 seed courses across 6 sessions with grades, evaluations, schedules, and teammates
- Active session: Determined by today's date (Jan-Apr = Hiver, May-Aug = Été, Sep-Dec = Automne). Courses are generated from
seed/pools.jsonunless the profile specifies otherwise - Past sessions: Completed courses with final grades
No authentication is required. The server accepts any Authorization: Bearer <token> header (or none at all).
- Course data: Edit
seed/courses.jsonand restart (or let--reloadhandle it) - Sessions, student info, programs, replaced days: Edit directly in
seed/ - Professors: Edit
seed/professors.json - Random generation pools: Edit
seed/pools.json(rooms, eval templates, schedule slots, course catalog) - Profiles: Edit
seed/profiles.json - Scenarios: Edit
seed/scenarios.json
The following changes in the Flutter app are needed to point it at this mock server. Do not commit these changes!
cd etsmobile-api-mock
python start.pyIn lib/domain/constants/urls.dart, replace the host with your local server:
// Before
static const String signetsAPI = "etsmobileapi.etsmtl.ca";
// After - use "10.0.2.2:8080" for Android emulator, "localhost:8080" for iOS emulator
static const String signetsAPI = "10.0.2.2:8080";The mock server runs in HTTP. In lib/data/services/signets-api/request_builder_service.dart, change Uri.https to Uri.http:
// Before
final uri = Uri.https(Urls.signetsAPI, endpoint, queryParameters);
// After
final uri = Uri.http(Urls.signetsAPI, endpoint, queryParameters);In lib/locator.dart, pass a local baseUrl to the SignetsClient:
// Before
locator.registerLazySingleton(() => SignetsClient(dio));
// After - match the host you used in step 2
locator.registerLazySingleton(() => SignetsClient(dio, baseUrl: 'http://10.0.2.2:8080/api/Etudiant/'));cd Notre-Dame
flutter runThe mock server requires no authentication, so you can skip past or stub out the login flow.
| Platform | Host to use |
|---|---|
| Android emulator | 10.0.2.2:8080 |
| iOS emulator | localhost:8080 |