@ -20,14 +20,14 @@ class SurveyWorkflowTest extends TestCase
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:', 'audit.enabled' => false]);
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:', 'audit.enabled' => false]);
DB::purge('sqlite');
DB::purge('sqlite');
$this->withoutMiddleware();
$this->withoutMiddleware();
foreach (['2025_06_30_105931_create_course_content_evaluation_asks_table.php', '2025_06_30_105932_create_course_content_evaluation_forms_table.php', '2025_07_03_093224_create_course_content_evaluations_table.php', '2026_01_13_094952_add_dimensions_to_course_content_evaluation_asks_table.php', '2026_10_09_000001_extend_survey_questions_and_snapshots.php'] as $migration) {
foreach (['2025_06_30_105931_create_course_content_evaluation_asks_table.php', '2025_06_30_105932_create_course_content_evaluation_forms_table.php', '2025_07_03_093224_create_course_content_evaluations_table.php', '2026_01_13_094952_add_dimensions_to_course_content_evaluation_asks_table.php', '2026_10_09_000001_extend_survey_questions_and_snapshots.php', '2026_10_12_000001_add_survey_audience.php' ] as $migration) {
(require database_path('migrations/' . $migration))->up();
(require database_path('migrations/' . $migration))->up();
}
}
foreach (['users', 'course_contents', 'teachers'] as $name) {
foreach (['users', 'course_contents', 'teachers'] as $name) {
Schema::create($name, function (Blueprint $table) { $table->id(); $table->softDeletes(); });
Schema::create($name, function (Blueprint $table) { $table->id(); $table->softDeletes(); });
}
}
$user = new User();
$user = new User();
$user->forceFill(['id' => 200]);
$user->forceFill(['id' => 200, 'mobile' => '13800000000' ]);
Auth::guard('mobile')->setUser($user);
Auth::guard('mobile')->setUser($user);
$admin = new Admin();
$admin = new Admin();
$admin->forceFill(['id' => 1, 'department_id' => 1]);
$admin->forceFill(['id' => 1, 'department_id' => 1]);
@ -62,6 +62,60 @@ class SurveyWorkflowTest extends TestCase
return $response->json('id');
return $response->json('id');
}
}
public function testAudienceAndDeadlineProtectEntryAndSubmission(): void
{
Schema::create('courses', function (Blueprint $table) { $table->id(); $table->softDeletes(); });
Schema::create('course_signs', function (Blueprint $table) { $table->id(); $table->integer('user_id'); $table->integer('course_id'); $table->integer('status'); $table->softDeletes(); });
DB::table('courses')->insert([['id' => 11], ['id' => 12]]);
$id = $this->createQuestion();
$detail = '/api/mobile/course/evaluation-detail?course_content_evaluation_id=10';
$submit = '/api/mobile/course/course-content-form';
$payload = ['course_content_evaluation_id' => 10, 'answer_version' => 2, 'data' => [['id' => $id, 'value' => ['count' => 0, 'follow' => 'no']]]];
$this->getJson($detail)->assertJsonMissing(['access_error' => '您还未报名相关课程']);
$this->postJson('/api/admin/course-content-evaluations/save', ['id' => 10, 'audience_course_ids' => [11, 12]])->assertJsonPath('audience_course_ids', [11, 12]);
foreach ([null, 0, 2, 4] as $status) {
DB::table('course_signs')->delete();
if ($status !== null) DB::table('course_signs')->insert(['user_id' => 200, 'course_id' => 12, 'status' => $status]);
$this->getJson($detail)->assertJsonPath('access_error', '您还未报名相关课程')->assertJsonMissing(['schema_hash']);
$this->postJson($submit, $payload)->assertJsonPath('access_error', '您还未报名相关课程');
$this->assertSame(0, DB::table('course_content_evaluation_forms')->count());
}
DB::table('course_signs')->update(['status' => 1]);
$this->assertNotEmpty($this->getJson($detail)->json('schema_hash'));
$replyId = $this->postJson($submit, $payload)->assertOk()->json('id');
$this->assertNotNull($replyId);
DB::table('course_content_evaluations')->where('id', 10)->update(['end_time' => '2000-01-01 00:00:00']);
$this->getJson($detail)->assertJsonPath('access_error', '当前问卷已截止');
$this->postJson($submit, $payload)->assertJsonPath('access_error', '当前问卷已截止');
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
DB::table('course_content_evaluations')->where('id', 10)->update(['end_time' => null]);
$this->postJson('/api/admin/course-content-evaluations/save', ['id' => 10, 'audience_course_ids' => []])->assertJsonPath('audience_course_ids', []);
DB::table('course_signs')->delete();
$this->assertNotEmpty($this->getJson($detail)->json('schema_hash'));
$this->postJson('/api/admin/course-content-evaluations/save', ['id' => 10, 'audience_course_ids' => [999]])->assertJsonPath('errmsg', '限定人群中存在无效课程');
}
public function testConditionsRejectBreakingEditsAndRollBack(): void
{
$base = '/api/admin/course-content-evaluation-ask/';
$parent = ['course_content_evaluation_id' => 10, 'name' => '保险', 'field' => 'insurance', 'edit_input' => 'radio', 'sort' => 1, 'select_item' => [['key' => 'yes', 'value' => '需要'], ['key' => 'no', 'value' => '不需要']]];
$parent['id'] = $this->postJson($base.'save', $parent)->assertOk()->json('id');
$child = ['course_content_evaluation_id' => 10, 'name' => '身份证', 'field' => 'idcard', 'edit_input' => 'text', 'sort' => 2, 'config' => ['visible_when' => ['field' => 'insurance', 'value' => 'yes']]];
$id = $this->postJson($base.'save', $child)->assertOk()->assertJsonPath('config.visible_when.value', 'yes')->json('id');
$parent['select_item'] = [['key' => 'no', 'value' => '不需要']];
$this->postJson($base.'save', $parent)->assertJsonPath('errmsg', '身份证:显示条件引用的选项不存在,请先调整条件');
$this->assertStringContainsString('yes', DB::table('course_content_evaluation_asks')->where('id', $parent['id'])->value('select_item'));
$this->getJson($base.'destroy?id='.$parent['id'])->assertJsonPath('errmsg', '身份证:显示条件只能引用本题之前的单选或多选题,请先调整条件');
$this->assertNull(DB::table('course_content_evaluation_asks')->where('id', $parent['id'])->value('deleted_at'));
$this->postJson($base.'reorder', ['course_content_evaluation_id' => 10, 'ids' => [$id, $parent['id']]])->assertJsonPath('errmsg', '身份证:显示条件只能引用本题之前的单选或多选题,请先调整条件');
$this->assertSame(1, (int) DB::table('course_content_evaluation_asks')->where('id', $parent['id'])->value('sort'));
$child['id'] = $id;
$child['config'] = [];
$this->postJson($base.'save', $child)->assertOk()->assertJsonPath('id', $id);
$this->getJson($base.'destroy?id='.$parent['id'])->assertOk();
$this->assertNotNull(DB::table('course_content_evaluation_asks')->where('id', $parent['id'])->value('deleted_at'));
}
public function testFullSubmissionStatisticsSnapshotAndStructureLock(): void
public function testFullSubmissionStatisticsSnapshotAndStructureLock(): void
{
{
$id = $this->createQuestion();
$id = $this->createQuestion();
@ -74,7 +128,8 @@ class SurveyWorkflowTest extends TestCase
->assertJsonPath('snapshot.questions.0.name', '融资对接');
->assertJsonPath('snapshot.questions.0.name', '融资对接');
$replyId = $reply->json('id');
$replyId = $reply->json('id');
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('errmsg', '您已提交过评价');
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('id', $replyId);
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
$this->getJson('/api/admin/course-content-evaluation-form/statistics?course_content_evaluation_id=10')
$this->getJson('/api/admin/course-content-evaluation-form/statistics?course_content_evaluation_id=10')
->assertJsonPath('responses', 1)->assertJsonPath('questions.0.dimensions.0.stats.valid_count', 1)
->assertJsonPath('responses', 1)->assertJsonPath('questions.0.dimensions.0.stats.valid_count', 1)
->assertJsonPath('questions.0.dimensions.0.stats.sum', 0)->assertJsonPath('questions.0.dimensions.1.stats.options.1.count', 1);
->assertJsonPath('questions.0.dimensions.0.stats.sum', 0)->assertJsonPath('questions.0.dimensions.1.stats.options.1.count', 1);
@ -112,6 +167,38 @@ class SurveyWorkflowTest extends TestCase
$this->getJson($url)->assertJsonPath('my_response', null);
$this->getJson($url)->assertJsonPath('my_response', null);
}
}
public function testResubmissionUpdatesSameReplyAndRequiresBoundProfile(): void
{
$id = $this->createQuestion();
$payload = ['course_content_evaluation_id' => 10, 'answer_version' => 2, 'data' => [['id' => $id, 'value' => ['count' => 0, 'follow' => 'no']]]];
$replyId = $this->postJson('/api/mobile/course/course-content-form', $payload)->assertOk()->json('id');
$payload['data'][0]['value'] = ['count' => 8, 'follow' => 'yes'];
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('id', $replyId)->assertJsonPath('data.0.value.count', 8);
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
$this->getJson('/api/admin/course-content-evaluation-form/statistics?course_content_evaluation_id=10')->assertJsonPath('responses', 1)->assertJsonPath('questions.0.dimensions.0.stats.sum', 8);
$payload['data'][0]['value']['count'] = -1;
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('errcode', 10001);
$this->getJson('/api/mobile/course/evaluation-detail?course_content_evaluation_id=10')->assertJsonPath('my_response.questions.0.value.count', 8);
DB::table('course_content_evaluations')->where('id', 10)->update(['end_time' => '2020-01-01 00:00:00']);
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('access_error', '当前问卷已截止');
DB::table('course_content_evaluations')->where('id', 10)->update(['end_time' => null]);
$guest = new User();
$guest->forceFill(['id' => 201]);
Auth::guard('mobile')->setUser($guest);
$this->getJson('/api/mobile/course/evaluation-detail?course_content_evaluation_id=10')->assertJsonPath('requires_profile', true)->assertJsonPath('my_response', null);
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('errmsg', '请先绑定或注册');
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
}
public function testProfileFieldConfigurationIsValidatedAndStored(): void
{
$payload = ['course_content_evaluation_id' => 10, 'name' => '姓名', 'field' => 'student_name', 'edit_input' => 'text', 'config' => ['user_field' => 'name']];
$this->postJson('/api/admin/course-content-evaluation-ask/save', $payload)->assertJsonPath('config.user_field', 'name');
$payload['field'] = 'invalid_field';
$payload['config']['user_field'] = 'password';
$this->postJson('/api/admin/course-content-evaluation-ask/save', $payload)->assertJsonPath('errcode', 10001);
}
public function testInvalidQuantityAndStaleSchemaDoNotCreateReplies(): void
public function testInvalidQuantityAndStaleSchemaDoNotCreateReplies(): void
{
{
$id = $this->createQuestion();
$id = $this->createQuestion();