You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
9.8 KiB

2 days ago
<?php
namespace Tests\Feature;
use App\Http\Controllers\Admin\CourseContentEvaluationAskController;
use App\Http\Controllers\Admin\CourseContentEvaluationsController;
use App\Models\Admin;
use App\Models\User;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class SurveyWorkflowTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:', 'audit.enabled' => false]);
DB::purge('sqlite');
$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) {
(require database_path('migrations/' . $migration))->up();
}
foreach (['users', 'course_contents', 'teachers'] as $name) {
Schema::create($name, function (Blueprint $table) { $table->id(); $table->softDeletes(); });
}
$user = new User();
$user->forceFill(['id' => 200]);
Auth::guard('mobile')->setUser($user);
$admin = new Admin();
$admin->forceFill(['id' => 1, 'department_id' => 1]);
Auth::guard('admin')->setUser($admin);
// The existing audit logger queries MySQL column comments; keep this integration test SQLite-only.
$this->app->bind(CourseContentEvaluationAskController::class, fn () => new class extends CourseContentEvaluationAskController {
protected function saveLogs($original, $model) {}
});
$this->app->bind(CourseContentEvaluationsController::class, fn () => new class extends CourseContentEvaluationsController {
protected function saveLogs($original, $model) {}
});
DB::table('course_content_evaluations')->insert(['id' => 10, 'title' => '培训问卷', 'status' => 1]);
}
protected function tearDown(): void
{
Auth::guard('mobile')->forgetUser();
Auth::guard('admin')->forgetUser();
DB::disconnect('sqlite');
parent::tearDown();
}
private function createQuestion(): int
{
$response = $this->postJson('/api/admin/course-content-evaluation-ask/save', [
'course_content_evaluation_id' => 10, 'name' => '融资对接', 'field' => 'finance', 'edit_input' => 'multi_dimension', 'sort' => 2,
'dimensions' => [
['field' => 'count', 'name' => '意向数量', 'edit_input' => 'number', 'need_fill' => true, 'config' => ['integer' => true, 'min' => 0]],
['field' => 'follow', 'name' => '需要跟进', 'edit_input' => 'radio', 'need_fill' => true, 'select_item' => [['key' => 'yes', 'value' => '是'], ['key' => 'no', 'value' => '否']]],
],
])->assertOk()->assertJsonPath('name', '融资对接');
return $response->json('id');
}
public function testFullSubmissionStatisticsSnapshotAndStructureLock(): void
{
$id = $this->createQuestion();
$definition = $this->getJson('/api/mobile/course/evaluation-detail?course_content_evaluation_id=10')->assertOk();
$hash = $definition->json('schema_hash');
$this->assertNotEmpty($hash);
$payload = ['course_content_evaluation_id' => 10, 'answer_version' => 2, 'schema_hash' => $hash, 'data' => [['id' => $id, 'name' => '伪造标题', 'value' => ['count' => 0, 'follow' => 'no']]]];
$reply = $this->postJson('/api/mobile/course/course-content-form', $payload)->assertOk()
->assertJsonPath('user_id', 200)->assertJsonPath('snapshot.version', 2)
->assertJsonPath('snapshot.questions.0.name', '融资对接');
$replyId = $reply->json('id');
$this->assertSame(1, DB::table('course_content_evaluation_forms')->count());
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('errmsg', '您已提交过评价');
$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('questions.0.dimensions.0.stats.sum', 0)->assertJsonPath('questions.0.dimensions.1.stats.options.1.count', 1);
$this->getJson('/api/admin/course-content-evaluation-form/show?id=' . $replyId)
->assertJsonPath('questions.0.dimensions.0.display_value', '0')
->assertJsonPath('questions.0.dimensions.1.display_value', '否');
$this->postJson('/api/admin/course-content-evaluation-ask/save', ['id' => $id, 'name' => '改名'])->assertJsonPath('errmsg', '已有答卷,不能修改题目结构;请新建问卷');
$this->getJson('/api/admin/course-content-evaluation-ask/destroy?id=' . $id)->assertJsonPath('errmsg', '已有答卷,不能删除题目');
$this->postJson('/api/admin/course-content-evaluation-ask/reorder', ['course_content_evaluation_id' => 10, 'ids' => [$id]])->assertJsonPath('errmsg', '已有答卷,不能调整题目顺序');
$this->postJson('/api/admin/course-content-evaluations/save', ['id' => 10, 'title' => '改标题'])->assertJsonPath('errmsg', '已有答卷,仅可调整开放时间与发布状态;请新建问卷修改内容');
$this->postJson('/api/admin/course-content-evaluations/save', ['id' => 10, 'status' => 0])->assertJsonPath('status', 0);
// Even an out-of-band definition change cannot rewrite an existing reply.
DB::table('course_content_evaluation_asks')->where('id', $id)->update(['name' => '数据库修改']);
$this->getJson('/api/admin/course-content-evaluation-form/show?id=' . $replyId)->assertJsonPath('questions.0.name', '融资对接');
}
public function testMobileReentryReturnsOnlyOwnSnapshot(): void
{
$id = $this->createQuestion();
$url = '/api/mobile/course/evaluation-detail?course_content_evaluation_id=10';
$this->getJson($url)->assertJsonPath('my_response', null);
$this->postJson('/api/mobile/course/course-content-form', [
'course_content_evaluation_id' => 10, 'answer_version' => 2,
'data' => [['id' => $id, 'value' => ['count' => 0, 'follow' => 'no']]],
])->assertOk();
DB::table('course_content_evaluations')->where('id', 10)->update(['title' => '新标题', 'status' => 0]);
DB::table('course_content_evaluation_asks')->where('id', $id)->update(['name' => '新题目']);
$this->getJson($url)->assertJsonPath('my_response.title', '培训问卷')
->assertJsonPath('my_response.questions.0.name', '融资对接')
->assertJsonPath('my_response.questions.0.value.count', 0)
->assertJsonPath('my_response.questions.0.dimensions.1.selected_keys.0', 'no');
$other = new User();
$other->forceFill(['id' => 201]);
Auth::guard('mobile')->setUser($other);
$this->getJson($url)->assertJsonPath('my_response', null);
}
public function testInvalidQuantityAndStaleSchemaDoNotCreateReplies(): void
{
$id = $this->createQuestion();
$payload = ['course_content_evaluation_id' => 10, 'answer_version' => 2, 'data' => [['id' => $id, 'value' => ['count' => -1, 'follow' => 'no']]]];
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('errcode', 10001);
$payload['schema_hash'] = 'stale';
$this->postJson('/api/mobile/course/course-content-form', $payload)->assertJsonPath('errmsg', '问卷已更新,请刷新后重新填写');
$this->assertSame(0, DB::table('course_content_evaluation_forms')->count());
}
public function testAtomicReorderRequiresCompleteList(): void
{
$id = $this->createQuestion();
$chapter = $this->postJson('/api/admin/course-content-evaluation-ask/save', ['course_content_evaluation_id' => 10, 'name' => '第五部分', 'field' => 'section', 'edit_input' => 'section', 'sort' => 1])->assertOk()->json('id');
$this->postJson('/api/admin/course-content-evaluation-ask/reorder', ['course_content_evaluation_id' => 10, 'ids' => [$id, $id]])->assertJsonPath('errmsg', '题目列表已变更,请刷新');
$this->postJson('/api/admin/course-content-evaluation-ask/reorder', ['course_content_evaluation_id' => 10, 'ids' => [$chapter, $id]])->assertJsonPath('msg', '排序已保存');
$this->assertSame([$chapter, $id], DB::table('course_content_evaluation_asks')->orderBy('sort')->pluck('id')->all());
}
public function testTextAnswersArePaginatedAndZeroRemainsVisible(): void
{
for ($i = 0; $i < 22; $i++) DB::table('course_content_evaluation_forms')->insert(['course_content_evaluation_id' => 10, 'data' => json_encode([['id' => 5, 'value' => $i === 0 ? '0' : '建议' . $i]])]);
$response = $this->getJson('/api/admin/course-content-evaluation-form/text-answers?course_content_evaluation_id=10&question_id=5&page=2&page_size=20')->assertJsonPath('total', 22);
$this->assertCount(2, $response->json('data'));
$this->assertSame('0', $response->json('data.1.text'));
}
public function testNewSurveyCanBeCreatedAndMigrationRolledBack(): void
{
$this->postJson('/api/admin/course-content-evaluations/save', ['title' => '新问卷', 'status' => 0])->assertJsonPath('title', '新问卷');
(require database_path('migrations/2026_10_09_000001_extend_survey_questions_and_snapshots.php'))->down();
$this->assertFalse(Schema::hasColumn('course_content_evaluation_forms', 'snapshot'));
$this->assertFalse(Schema::hasColumn('course_content_evaluation_asks', 'config'));
}
}