api()->loginWebAndGetToken( DatabaseSeeder::ADMIN_USERNAME, DatabaseSeeder::PASSWORD )['token']; $f = $this->fixture(); $res = $this->api()->withToken($token, 'POST', 'questions', [ 'questionnaireID' => $f->questionnaireId, 'questionKey' => 'extra_q', 'defaultText' => 'Extra question text', 'type' => 'free_text', 'isRequired' => 0, 'configJson' => '{}', ]); $this->assertApiOk($res); $this->assertSame('extra_q', $res['data']['question']['questionKey'] ?? ''); } public function testAdminCreatesAnswerOption(): void { $token = $this->api()->loginWebAndGetToken( DatabaseSeeder::ADMIN_USERNAME, DatabaseSeeder::PASSWORD )['token']; $f = $this->fixture(); $res = $this->api()->withToken($token, 'POST', 'answer_options', [ 'questionID' => $f->questionId, 'optionKey' => 'no_option', 'defaultText' => 'No', 'points' => 0, ]); $this->assertApiOk($res); $this->assertSame('no_option', $res['data']['answerOption']['optionKey'] ?? ''); } public function testAnswerOptionsRequireQuestionId(): void { $token = $this->api()->loginWebAndGetToken( DatabaseSeeder::ADMIN_USERNAME, DatabaseSeeder::PASSWORD )['token']; $res = $this->api()->withToken($token, 'GET', 'answer_options'); $this->assertApiError($res, 'MISSING_PARAM', 400); } public function testSavingNoteBeforeBumpsStructureRevisionAndExportsKey(): void { $adminToken = $this->api()->loginWebAndGetToken( DatabaseSeeder::ADMIN_USERNAME, DatabaseSeeder::PASSWORD )['token']; $coachLogin = $this->api()->loginMobileAndGetToken( DatabaseSeeder::COACH_USERNAME, DatabaseSeeder::PASSWORD ); $f = $this->fixture(); $before = $this->api()->withMobileToken($coachLogin['token'], 'GET', 'app_questionnaires', null, [ 'id' => $f->questionnaireId, ]); $this->assertApiOk($before); $revBefore = (int)($before['data']['structureRevision'] ?? 1); $put = $this->api()->withToken($adminToken, 'PUT', 'questions', [ 'questionID' => $f->questionId, 'questionKey' => 'consent_q', 'defaultText' => 'Consent?', 'type' => 'single_choice_question', 'isRequired' => 1, 'configJson' => json_encode(['questionKey' => 'consent_q'], JSON_UNESCAPED_UNICODE), 'noteBefore' => 'Bitte dem Klienten deutlich machen: Ohne Einwilligung keine Teilnahme.', 'noteAfter' => '', ]); $this->assertApiOk($put); $revAfter = (int)($put['data']['structureRevision'] ?? 0); $this->assertGreaterThan($revBefore, $revAfter, 'note-only edit must bump structureRevision for app sync'); $detail = $this->api()->withMobileToken($coachLogin['token'], 'GET', 'app_questionnaires', null, [ 'id' => $f->questionnaireId, ]); $this->assertApiOk($detail); $questions = $detail['data']['questions'] ?? []; $this->assertIsArray($questions); $matched = null; foreach ($questions as $q) { if (($q['question'] ?? '') === 'consent_q' || ($q['id'] ?? '') === 'q1') { $matched = $q; break; } } $this->assertNotNull($matched, 'expected consent question in app export'); $this->assertSame('consent_q_note_before', $matched['noteBeforeKey'] ?? null); $de = $detail['data']['translations']['de'] ?? []; $this->assertSame( 'Bitte dem Klienten deutlich machen: Ohne Einwilligung keine Teilnahme.', $de['consent_q_note_before'] ?? null ); } }