refactor: 修复知识库和操作指引

This commit is contained in:
2026-03-13 14:32:37 +08:00
parent bbe8e60646
commit 58f42de9df
88 changed files with 3387 additions and 2472 deletions

View File

@@ -1,246 +0,0 @@
<?php
use App\Models\SopTemplate;
use App\Models\User;
use App\Services\SopTemplateService;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
$this->actingAs($this->user);
$this->service = new SopTemplateService();
});
test('可以导出模板为 JSON', function () {
$template = SopTemplate::factory()->create([
'name' => '测试模板',
'version' => '1.0.0',
]);
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'content' => '内容',
'sort_order' => 1,
]);
$json = $this->service->exportToJson($template);
$data = json_decode($json, true);
expect($data)->toHaveKey('template')
->and($data)->toHaveKey('steps')
->and($data['template']['name'])->toBe('测试模板')
->and($data['steps'])->toHaveCount(1);
});
test('导出的 JSON 包含步骤信息', function () {
$template = SopTemplate::factory()->create();
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'content' => '<p>这是内容</p>',
'sort_order' => 1,
'is_required' => true,
]);
$json = $this->service->exportToJson($template);
$data = json_decode($json, true);
expect($data['steps'][0])->toHaveKey('step_number')
->and($data['steps'][0])->toHaveKey('title')
->and($data['steps'][0])->toHaveKey('content')
->and($data['steps'][0]['title'])->toBe('第一步');
});
test('可以从 JSON 导入模板', function () {
$jsonData = [
'template' => [
'name' => '导入的模板',
'description' => '这是描述',
'category' => '测试分类',
'version' => '1.0.0',
],
'steps' => [
[
'step_number' => 1,
'title' => '第一步',
'content' => '内容',
'sort_order' => 1,
'is_required' => true,
],
],
];
$json = json_encode($jsonData);
$template = $this->service->importFromJson($json);
expect($template)->toBeInstanceOf(SopTemplate::class)
->and($template->name)->toBe('导入的模板')
->and($template->status)->toBe('draft')
->and($template->steps)->toHaveCount(1);
});
test('导入时会创建步骤', function () {
$jsonData = [
'template' => [
'name' => '测试模板',
'version' => '1.0.0',
],
'steps' => [
[
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
],
[
'step_number' => 2,
'title' => '第二步',
'sort_order' => 2,
],
],
];
$json = json_encode($jsonData);
$template = $this->service->importFromJson($json);
expect($template->steps)->toHaveCount(2)
->and($template->steps->first()->title)->toBe('第一步')
->and($template->steps->last()->title)->toBe('第二步');
});
test('导入无效 JSON 会抛出异常', function () {
$invalidJson = 'invalid json';
$this->service->importFromJson($invalidJson);
})->throws(\Illuminate\Validation\ValidationException::class);
test('导入缺少必需字段会抛出异常', function () {
$jsonData = [
'template' => [
'name' => '测试模板',
// 缺少 version
],
'steps' => [],
];
$json = json_encode($jsonData);
$this->service->importFromJson($json);
})->throws(\Illuminate\Validation\ValidationException::class);
test('可以发布模板', function () {
$template = SopTemplate::factory()->create([
'status' => 'draft',
]);
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$this->service->publish($template, '首次发布');
expect($template->fresh()->status)->toBe('published')
->and($template->fresh()->published_at)->not->toBeNull()
->and($template->versions)->toHaveCount(1);
});
test('发布时会创建版本快照', function () {
$template = SopTemplate::factory()->create([
'status' => 'draft',
'version' => '1.0.0',
]);
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$this->service->publish($template, '测试发布');
$version = $template->versions->first();
expect($version)->not->toBeNull()
->and($version->version)->toBe('1.0.0')
->and($version->change_log)->toBe('测试发布')
->and($version->content_snapshot)->toHaveKey('template')
->and($version->content_snapshot)->toHaveKey('steps');
});
test('可以归档模板', function () {
$template = SopTemplate::factory()->create([
'status' => 'published',
]);
$this->service->archive($template);
expect($template->fresh()->status)->toBe('archived');
});
test('可以复制模板', function () {
$template = SopTemplate::factory()->create([
'name' => '原始模板',
]);
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$newTemplate = $this->service->duplicate($template, '复制的模板');
expect($newTemplate->name)->toBe('复制的模板')
->and($newTemplate->status)->toBe('draft')
->and($newTemplate->steps)->toHaveCount(1)
->and($newTemplate->id)->not->toBe($template->id);
});
test('复制模板时会复制所有步骤', function () {
$template = SopTemplate::factory()->create();
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$template->steps()->create([
'step_number' => 2,
'title' => '第二步',
'sort_order' => 2,
]);
$newTemplate = $this->service->duplicate($template, '复制的模板');
expect($newTemplate->steps)->toHaveCount(2)
->and($newTemplate->steps->first()->title)->toBe('第一步')
->and($newTemplate->steps->last()->title)->toBe('第二步');
});
test('复制模板时会复制交互任务', function () {
$template = SopTemplate::factory()->create();
$step = $template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$step->interactiveTasks()->create([
'task_type' => 'confirm',
'task_config' => ['message' => '确认?'],
'is_required' => true,
]);
$newTemplate = $this->service->duplicate($template, '复制的模板');
$newStep = $newTemplate->steps->first();
expect($newStep->interactiveTasks)->toHaveCount(1)
->and($newStep->interactiveTasks->first()->task_type)->toBe('confirm');
});

View File

@@ -1,200 +0,0 @@
<?php
use App\Models\SopTemplate;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->user = User::factory()->create();
$this->actingAs($this->user);
});
test('可以创建 SOP 模板', function () {
$template = SopTemplate::factory()->create([
'name' => '测试模板',
'status' => 'draft',
'created_by' => $this->user->id,
]);
expect($template->name)->toBe('测试模板')
->and($template->status)->toBe('draft')
->and($template->created_by)->toBe($this->user->id);
});
test('可以为模板添加步骤', function () {
$template = SopTemplate::factory()->create();
$step = $template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'content' => '这是第一步的内容',
'sort_order' => 1,
'is_required' => true,
]);
expect($template->steps)->toHaveCount(1)
->and($step->title)->toBe('第一步')
->and($step->template->id)->toBe($template->id);
});
test('步骤按 sort_order 排序', function () {
$template = SopTemplate::factory()->create();
$template->steps()->create([
'step_number' => 3,
'title' => '第三步',
'sort_order' => 3,
]);
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$template->steps()->create([
'step_number' => 2,
'title' => '第二步',
'sort_order' => 2,
]);
$steps = $template->fresh()->steps;
expect($steps)->toHaveCount(3)
->and($steps->first()->title)->toBe('第一步')
->and($steps->last()->title)->toBe('第三步');
});
test('可以为步骤添加交互任务', function () {
$template = SopTemplate::factory()->create();
$step = $template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$task = $step->interactiveTasks()->create([
'task_type' => 'confirm',
'task_config' => ['message' => '确认完成?'],
'validation_rules' => [],
'is_required' => true,
]);
expect($step->interactiveTasks)->toHaveCount(1)
->and($task->task_type)->toBe('confirm')
->and($task->step->id)->toBe($step->id);
});
test('模板状态可以从草稿变为已发布', function () {
$template = SopTemplate::factory()->create([
'status' => 'draft',
]);
$template->update([
'status' => 'published',
'published_at' => now(),
]);
expect($template->fresh()->status)->toBe('published')
->and($template->fresh()->published_at)->not->toBeNull();
});
test('模板状态可以从已发布变为已归档', function () {
$template = SopTemplate::factory()->create([
'status' => 'published',
'published_at' => now(),
]);
$template->update(['status' => 'archived']);
expect($template->fresh()->status)->toBe('archived');
});
test('可以创建模板版本快照', function () {
$template = SopTemplate::factory()->create();
$template->steps()->create([
'step_number' => 1,
'title' => '第一步',
'sort_order' => 1,
]);
$version = $template->versions()->create([
'version' => '1.0.0',
'change_log' => '首次发布',
'content_snapshot' => [
'template' => $template->toArray(),
'steps' => $template->steps->toArray(),
],
'created_by' => $this->user->id,
'created_at' => now(),
]);
expect($template->versions)->toHaveCount(1)
->and($version->version)->toBe('1.0.0')
->and($version->content_snapshot)->toHaveKey('template')
->and($version->content_snapshot)->toHaveKey('steps');
});
test('模板记录活动日志', function () {
$template = SopTemplate::factory()->create([
'name' => '原始名称',
]);
$template->update(['name' => '新名称']);
$activities = $template->activities;
expect($activities)->toHaveCount(2) // created 和 updated
->and($activities->last()->description)->toContain('updated');
});
test('可以通过分类筛选模板', function () {
SopTemplate::factory()->create(['category' => '安全操作']);
SopTemplate::factory()->create(['category' => '设备维护']);
SopTemplate::factory()->create(['category' => '安全操作']);
$safetyTemplates = SopTemplate::where('category', '安全操作')->get();
expect($safetyTemplates)->toHaveCount(2);
});
test('可以通过状态筛选模板', function () {
SopTemplate::factory()->create(['status' => 'draft']);
SopTemplate::factory()->create(['status' => 'published']);
SopTemplate::factory()->create(['status' => 'draft']);
$draftTemplates = SopTemplate::where('status', 'draft')->get();
expect($draftTemplates)->toHaveCount(2);
});
test('模板的标签字段正确转换为数组', function () {
$template = SopTemplate::factory()->create([
'tags' => ['标签1', '标签2', '标签3'],
]);
expect($template->fresh()->tags)->toBeArray()
->and($template->fresh()->tags)->toHaveCount(3)
->and($template->fresh()->tags)->toContain('标签1');
});
test('模板的适用部门字段正确转换为数组', function () {
$template = SopTemplate::factory()->create([
'applicable_departments' => ['生产部', '质检部'],
]);
expect($template->fresh()->applicable_departments)->toBeArray()
->and($template->fresh()->applicable_departments)->toHaveCount(2);
});
test('可以软删除模板', function () {
$template = SopTemplate::factory()->create();
$templateId = $template->id;
$template->delete();
expect(SopTemplate::find($templateId))->toBeNull()
->and(SopTemplate::withTrashed()->find($templateId))->not->toBeNull();
});

View File

@@ -21,13 +21,13 @@ class TerminalResourceTest extends TestCase
protected function setUp(): void
{
parent::setUp();
// 创建测试用户并赋予管理员权限
$this->user = User::factory()->create();
// 设置为Filament管理员
config(['filament.auth.guard' => 'web']);
$this->actingAs($this->user);
}
@@ -62,7 +62,7 @@ class TerminalResourceTest extends TestCase
'code' => 'TEST-0001',
'ip_address' => '192.168.1.100',
'station_id' => 1,
'diagram_url' => 'https://example.com/diagram.png',
'diagram_url' => 'https://example.com/diagram.html',
'display_config' => [
'resolution' => '1920x1080',
'refresh_rate' => '60',
@@ -250,7 +250,7 @@ class TerminalResourceTest extends TestCase
// 测试分组功能是否可用
$component = Livewire::test(ListTerminals::class);
// 验证表格可以正常渲染
$component->assertSuccessful();
}

View File

@@ -48,7 +48,7 @@ class TerminalSyncTest extends TestCase
// 断言任务已加入队列
Queue::assertPushed(SyncTerminalConfigJob::class, function ($job) use ($terminal, $log) {
return $job->terminal->id === $terminal->id
return $job->terminal->id === $terminal->id
&& $job->log->id === $log->id;
});
}
@@ -64,7 +64,7 @@ class TerminalSyncTest extends TestCase
'code' => 'TEST-002',
'ip_address' => '192.168.1.100',
'station_id' => 1,
'diagram_url' => 'https://example.com/diagram.png',
'diagram_url' => 'https://example.com/diagram.html',
'display_config' => ['resolution' => '1920x1080'],
]);
@@ -117,7 +117,7 @@ class TerminalSyncTest extends TestCase
// 断言创建了3个同步日志
$this->assertCount(3, $logs);
// 断言每个终端都有同步日志
foreach ($terminals as $terminal) {
$this->assertDatabaseHas('terminal_sync_logs', [