Skip to content

MissionController testing - 22/05/2026

  • Le if (!$user) -> 401 present dans les 12 methodes est inatteignable : le middleware auth:api du groupe renvoie deja 401 avant d’entrer dans le controller.
  • 404 morts : dans acceptDeclineMissions, getMissionDetails, cancelMissionByWorker, le mission_id porte la regle exists:missions,id. Le find() qui suit trouve donc toujours la mission -> le branchement if (!$mission) -> 404 est inatteignable. (putInProgress est l’exception : son 404 couvre le cas “pas owner”, il est bien atteignable.)
  • acceptDeclineMissions fait ~200 lignes : toute la branche accept (creation assignment, MAJ broadcasts, mission_code, chat, Redis, notifications, stats) devrait etre extraite dans MissionService — comme cancelMission / releaseAssignment le sont deja.
  • Trou d’autorisation — cancelMissionByOwner : la methode ne verifie PAS que l’utilisateur connecte est l’owner de la mission. N’importe quel utilisateur authentifie peut annuler n’importe quelle mission ayant un assignment pending. A corriger.
  • getMissionDetails n’applique aucun scope : tout utilisateur authentifie peut consulter n’importe quelle mission.
  • getOwnMissionsV2 : le statut waiting_feedback apparait a la fois dans tab=0 (actives) et tab=1 (terminees)
  • Comparaisons fragiles a la chaine 'null' ($mission->latitude != 'null') : sentinelle texte au lieu d’un vrai null SQL.
  • Bug getAcceptedMissions : le controller reassigne $mission->owner = [bloc resume], mais la relation owner est eager-loaded -> dans toArray() la relation (User complet) ecrase le tableau resume. Le bloc resume {business_name, manager_name} n’est jamais visible cote response. A corriger (renommer la cle, ou ne pas eager-load owner).
  • Branche morte updateMyMission : le garde autorise les statuts {open, pending}, mais la colonne missions.status est un enum sans pending — une mission ne peut jamais etre pending. Le pending du in_array est inatteignable.
  • createMissionV2 passe $mission->latitude / longitude bruts a AdminInstantMissionOpened (sans cast), contrairement aux autres appels du meme event qui castent en (float).

2. Tests de chaque route — approche features-first (non-unit)

Section titled “2. Tests de chaque route — approche features-first (non-unit)”

Routes du groupe ['auth:api', 'banned', 'verified', 'onboarding'].

2.1 createMissionV2 — POST /api/create-mission-v2

Section titled “2.1 createMissionV2 — POST /api/create-mission-v2”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • looking_id manquant / non integer / inexistant -> 422
  • date manquante / format invalide -> 422
  • start_time / end_time manquants ou pas au format H:i -> 422
  • rate_per_hour manquant / non numeric / negatif -> 422
  • requirement_id manquant / non string / > 255 -> 422
  • priorities > 255 -> 422
  • enterprise_id fourni mais inexistant -> 422
  • is_schedule non booleen -> 422

Logique metier / effets observables

  • happy path : delegue a MissionService::createMission($user, $data) (creation mission + requirements + matching) ; broadcast AdminInstantMissionOpened ; 200 avec la mission (relation enterprise chargee)
  • si MissionService::createMission jette une InvalidArgumentException -> 422 avec le message de l’exception
  • les regles metier owner/enterprise sont portees par MissionService::createMission (a tester cote service)

2.2 acceptDeclineMissions — POST /api/accept-decline-missions

Section titled “2.2 acceptDeclineMissions — POST /api/accept-decline-missions”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422
  • status manquant ou hors {0, 1} -> 422

Gardes metier

  • status=1 et le worker a deja un MissionAssignment pending -> 422 (“You already have a mission in progress or pending.”)
  • la mission a deja un MissionAssignment (peu importe le worker) -> 422 (“Mission already assigned.”)

Logique metier — accept (status=1)

  • cree un MissionAssignment pending pour le worker connecte
  • les autres MissionWorkerBroadcast pending passent declined, celui du worker passe accepted
  • broadcast RemoveMission aux autres workers ; les broadcasts non-accepted sont supprimes
  • la mission passe status = matched + mission_code genere (entier 1000-9999) ; broadcast AdminInstantMissionClosed
  • ouverture du chat (ChatService::createChat)
  • cleanup Redis (removeMissionOpen, removeWorkerOnline) + recompute tension si lat/lng valides — echec catche/logue, la requete reussit
  • broadcast MissionAccepted ; notification a l’owner — echecs catches/logues
  • WorkerProfile.ready_to_work = 0 ; broadcast WorkerStatusChanged (statut MATCHED) ; StatsService::onMissionMatched
  • reponse 200 : missionData (mission + distance_km worker<->mission + bloc owner_detail)

Logique metier — decline (status=0)

  • le MissionWorkerBroadcast du worker passe declined ; aucun autre effet ; 200

2.3 getMissionIds — GET /api/get-mission-ids

Section titled “2.3 getMissionIds — GET /api/get-mission-ids”

Auth

  • utilisateur non authentifie -> 401

Garde metier

  • worker sans WorkerProfile ou ready_to_work falsy -> 403 (“You must be ready to work to view missions.”)

Logique metier / effets observables

  • worker ready_to_work -> renvoie les IDs des missions matchables (MatchingService::getMatchingMissionsForWorker), tries par score decroissant
  • 200

2.4 getOwnMissionsV2 — GET /api/get-own-missionsV2

Section titled “2.4 getOwnMissionsV2 — GET /api/get-own-missionsV2”

Auth

  • utilisateur non authentifie -> 401

Logique metier / effets observables

  • liste paginee (10 par page) des missions de l’owner connecte (owner_id)
  • tab=0 (defaut) -> statuts open / matched / in_progress / waiting_feedback
  • tab=1 -> statuts completed / waiting_feedback
  • chaque mission porte un flag given_feedback (bool : l’owner a-t-il deja note)
  • triees par id decroissant
  • liste non vide -> reponse paginee (nextpage = page + 1) ; liste vide -> data: [] + message “No Mission.”

2.5 finishOwnMission — POST /api/finish-mission

Section titled “2.5 finishOwnMission — POST /api/finish-mission”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422

Gardes metier

  • mission inexistante pour cet owner, OU non possedee par le user, OU status != in_progress -> 422 (“Mission not found or not in progress”)

Logique metier / effets observables

  • happy path : delegue a MissionService::progress_ended($mission->id) ; 200 avec mission_id

2.6 getMissionDetails — GET /api/get-mission-details

Section titled “2.6 getMissionDetails — GET /api/get-mission-details”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422

Logique metier / effets observables

  • renvoie la mission + relations (looking, owner, enterprise, feedbacks, missionAssignment)
  • matching_time = missionAssignment.created_at ou null si pas d’assignment
  • bloc owner_details avec les cles : [first_name, last_name, business_name, manager_firstname, manager_lastname, profile_photo, address, enterprise_id]
  • given_feedback : bool selon que le user courant a deja note
  • aucun scope : tout utilisateur authentifie peut consulter n’importe quelle mission (cf section 1)

2.7 getAcceptedMissions — GET /api/get-acceped-missions

Section titled “2.7 getAcceptedMissions — GET /api/get-acceped-missions”

Auth

  • utilisateur non authentifie -> 401

Logique metier / effets observables

  • liste paginee (10 par page) des missions assignees au worker connecte (via mission_assignments.worker_id), hors statut cancel
  • triees par id decroissant
  • chaque mission : flag given_feedback
  • le controller tente de remplacer owner par un bloc resume {first_name, last_name, business_name, manager_name}, mais la relation owner eager-loaded ecrase ce tableau (cf bug section 1) : la response renvoie le User complet. Le test pin ce comportement actuel.
  • liste non vide -> reponse paginee (nextpage = page + 1) ; liste vide -> data: [] + message “No Mission.”

2.8 putInProgress — POST /api/put-inprogress

Section titled “2.8 putInProgress — POST /api/put-inprogress”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422
  • mission_code manquant / non integer -> 422

Gardes metier

  • mission non possedee par le user connecte -> 404

Logique metier / effets observables

  • delegue a MissionService::put_inprogress($mission_id, $code)
  • exception du service -> 500 (“Failed to start mission.”)
  • resultat invalid_code -> 422 (“Invalid mission code.”)
  • resultat not_matched -> 422 (“Mission is not in matched state.”)
  • sinon (mission retournee) -> 200 avec mission_id

2.9 cancelMissionV2 — POST /api/cancel-missionV2

Section titled “2.9 cancelMissionV2 — POST /api/cancel-missionV2”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422
  • reason_id manquant / non integer / inexistant dans report_reasons -> 422
  • note non string -> 422

Gardes metier

  • mission dont owner_id != user -> 403
  • mission dont le status est hors {open, matched} -> 422 (“Mission cannot be canceled in its current state.”)

Logique metier / effets observables

  • delegue a MissionService::cancelMission($mission, $reason_id, $note) ; broadcast AdminInstantMissionClosed
  • exception du service -> 500 (“Failed to cancel mission.”)
  • resultat avec error = mission_already_confirmed -> 422 (“Mission cannot be canceled as it is already confirmed.”)
  • sinon -> 200

2.10 cancelMissionByWorker — POST /api/cancel-mission-by-worker

Section titled “2.10 cancelMissionByWorker — POST /api/cancel-mission-by-worker”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422

Gardes metier

  • pas de MissionAssignment pending pour (cette mission, le worker connecte) -> 422 (“You Could not cancel mission .”)
  • mission status = cancel -> 422 (“Mission is already canceled.”)
  • mission status = in_progress -> 422 (“Mission is in progress and cannot be canceled.”)

Logique metier / effets observables

  • delegue a MissionService::releaseAssignment($mission, $worker_id, initiatedByWorker: true)
  • exception du service -> 500 (“Failed to cancel assignment.”)
  • succes -> 200 (“Mission reopened successfully after worker cancellation.”)

2.11 cancelMissionByOwner — POST /api/cancel-mission-by-owner

Section titled “2.11 cancelMissionByOwner — POST /api/cancel-mission-by-owner”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422

Gardes metier

  • pas de MissionAssignment pending sur la mission (peu importe le worker) -> 422 (“You Could not cancel mission .”)
  • mission status = cancel -> 422 (“Mission is already canceled.”)
  • mission status = in_progress -> 422 (“Mission is in progress and cannot be canceled.”)
  • manquant : aucun controle que le user connecte est l’owner de la mission (cf section 1) — comportement actuel a pinner, fix d’autorisation a prevoir

Logique metier / effets observables

  • delegue a MissionService::releaseAssignment($mission, $worker_id, initiatedByWorker: false) ; worker_id = celui de l’assignment pending
  • exception du service -> 500 (“Failed to cancel assignment.”)
  • succes -> 200 (“Mission reopened successfully after owner cancellation.”)

2.12 updateMyMission — POST /api/update-my-mission

Section titled “2.12 updateMyMission — POST /api/update-my-mission”

Auth

  • utilisateur non authentifie -> 401

Validation des inputs

  • mission_id manquant / non integer / inexistant -> 422
  • date format invalide -> 422
  • start_time / end_time pas au format H:i:s -> 422
  • duration / rate_per_hour non numeric -> 422
  • description non string -> 422

Gardes metier

  • mission dont owner_id !== user -> 403
  • mission dont le status est hors {open, pending} -> 422 (mission non editable)

Logique metier / effets observables

  • update partiel : seuls date / start_time / end_time / rate_per_hour / description presents dans le payload sont modifies ; un champ absent garde sa valeur
  • recalcul duration via MissionService::computeDuration si start_time ou end_time est fourni (valeurs fusionnees avec l’existant)
  • si la mission est open apres save -> SyncMissionMatching::dispatch($mission->id, contentChanged: true)
  • reponse : 200 avec la mission rechargee (relations looking, owner, feedbacks)