Local mock server that replicates the ETSMobileAPI for testing the ÉTSMobile Flutter app.
- Quick Start
- 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.
The 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/infoEtudiantAll 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"},
)The easiest way to pick a profile is python start.py. You can also set the PROFILE environment variable directly:
PROFILE=semester-off uvicorn main:app --port 8080 --reload --reload-include "*.json"| 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 + LOG410 only |
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 also set these values directly with environment variables, from any profile:
| Variable | Description | Example |
|---|---|---|
COURSE_COUNT |
Number of courses (1-5) | COURSE_COUNT=2 |
SCHEDULE_DAYS |
Comma-separated day codes (1=Mon, 6=Sat) | SCHEDULE_DAYS=1,3,5 |
TIME_PREFERENCE |
morning, afternoon, evening (comma-separated for multiple) |
TIME_PREFERENCE=morning,evening |
COURSE_COUNT=2 SCHEDULE_DAYS=1,3,5 uvicorn main:app --port 8080 --reload --reload-include "*.json"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:
SEMESTER_WEEK=3 uvicorn main:app --port 8080 --reload --reload-include "*.json"This 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 set the SCENARIO environment variable:
SCENARIO=semaine-relache uvicorn main:app --port 8080 --reload --reload-include "*.json"| 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 flaky-network and broken-server conditions. Failures are configured at startup via env vars and can be tweaked at runtime through /admin/failures.
| Variable | Effect | Example |
|---|---|---|
LATENCY_MS |
Add latency before every API response. Accepts a fixed int or a min-max range. |
LATENCY_MS=500 or LATENCY_MS=100-800 |
ERROR_RATE |
Probability (0.0-1.0) that any API call returns a 500. | ERROR_RATE=0.1 |
FAIL_ENDPOINTS |
Comma-separated endpoint names that always return 503. Use * for all endpoints. |
FAIL_ENDPOINTS=listeCoequipiers,lireEvaluationCours |
TIMEOUT_ENDPOINTS |
Endpoints that hang the request. | TIMEOUT_ENDPOINTS=lireEvaluationCours |
TIMEOUT_DURATION_S |
How long timeout endpoints sleep before giving up with a 504. | TIMEOUT_DURATION_S=30 (default 60) |
MALFORMED |
Truncate every successful 2xx response body in half. | MALFORMED=true |
AUTH_REQUIRED |
Return 401 on API requests that lack an Authorization header. |
AUTH_REQUIRED=true |
LATENCY_MS=200-600 ERROR_RATE=0.1 uvicorn main:app --port 8080 --reload# 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 |