createMock(SecurityCheckTemplateRepository::class); $repo->expects($this->never())->method('insert'); $service = new SecurityCheckTemplateService($repo); $result = $service->create(self::TENANT_ID, '', 'Name', 1); $this->assertFalse($result['ok']); $this->assertArrayHasKey('product_code', $result['errors']); } public function testCreateRejectsInvalidCode(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $service = new SecurityCheckTemplateService($repo); $result = $service->create(self::TENANT_ID, 'has spaces!', 'Name', 1); $this->assertFalse($result['ok']); $this->assertArrayHasKey('product_code', $result['errors']); } public function testCreateRejectsDuplicateCode(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('codeExists')->willReturn(true); $service = new SecurityCheckTemplateService($repo); $result = $service->create(self::TENANT_ID, 'intranet', 'Intranet', 1); $this->assertFalse($result['ok']); $this->assertArrayHasKey('product_code', $result['errors']); } public function testCreateSucceeds(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('codeExists')->willReturn(false); $repo->method('insert')->willReturn(12); $service = new SecurityCheckTemplateService($repo); $result = $service->create(self::TENANT_ID, 'intranet', 'Intranet', 1); $this->assertTrue($result['ok']); $this->assertSame(12, $result['id']); } public function testSaveTechSchemaRejectsMissingTemplate(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('findById')->willReturn(null); $repo->expects($this->never())->method('updateSchema'); $service = new SecurityCheckTemplateService($repo); $result = $service->saveTechSchema(self::TENANT_ID, 99, [['type' => 'check', 'label' => 'X']], 1); $this->assertFalse($result['ok']); } public function testSaveTechSchemaSlugifiesKeysBumpsVersionAndKeepsSections(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('findById')->willReturn(['id' => 1, 'version' => 3]); $capturedJson = null; $capturedVersion = null; $repo->method('updateSchema')->willReturnCallback(function (int $t, int $id, string $json, int $version) use (&$capturedJson, &$capturedVersion) { $capturedJson = $json; $capturedVersion = $version; return true; }); $service = new SecurityCheckTemplateService($repo); $result = $service->saveTechSchema(self::TENANT_ID, 1, [ ['type' => 'section', 'label' => 'Intranet'], ['type' => 'check', 'label' => 'TLS überall erzwungen', 'hint' => 'check certs'], ], 1); $this->assertTrue($result['ok']); $this->assertSame(4, $capturedVersion); $data = json_decode((string) $capturedJson, true); $this->assertSame(4, $data['version']); $this->assertSame('section', $data['items'][0]['type']); $this->assertSame('check', $data['items'][1]['type']); $this->assertSame('tls_ueberall_erzwungen', $data['items'][1]['key']); $this->assertSame('check certs', $data['items'][1]['hint']); } public function testSaveTechSchemaPersistsMarkdownAndOmitsEmpty(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]); $capturedJson = null; $repo->method('updateSchema')->willReturnCallback(function (int $t, int $id, string $json) use (&$capturedJson) { $capturedJson = $json; return true; }); $service = new SecurityCheckTemplateService($repo); $service->saveTechSchema(self::TENANT_ID, 1, [ ['type' => 'check', 'label' => 'TLS', 'markdown' => '**Verify** the certificate chain.'], ['type' => 'check', 'label' => 'Backup', 'markdown' => ' '], ], 1); $data = json_decode((string) $capturedJson, true); $this->assertSame('**Verify** the certificate chain.', $data['items'][0]['markdown']); $this->assertArrayNotHasKey('markdown', $data['items'][1]); } public function testSaveTechSchemaDeduplicatesKeys(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]); $capturedJson = null; $repo->method('updateSchema')->willReturnCallback(function (int $t, int $id, string $json) use (&$capturedJson) { $capturedJson = $json; return true; }); $service = new SecurityCheckTemplateService($repo); $service->saveTechSchema(self::TENANT_ID, 1, [ ['type' => 'check', 'label' => 'Backup'], ['type' => 'check', 'label' => 'Backup'], ], 1); $data = json_decode((string) $capturedJson, true); $this->assertSame('backup', $data['items'][0]['key']); $this->assertSame('backup_2', $data['items'][1]['key']); } public function testSaveTechSchemaRejectsItemWithoutLabel(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]); $repo->expects($this->never())->method('updateSchema'); $service = new SecurityCheckTemplateService($repo); $result = $service->saveTechSchema(self::TENANT_ID, 1, [['type' => 'check', 'label' => '']], 1); $this->assertFalse($result['ok']); $this->assertArrayHasKey('schema', $result['errors']); } public function testSaveTechSchemaRejectsTooManyItems(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->method('findById')->willReturn(['id' => 1, 'version' => 1]); $items = []; for ($i = 0; $i <= SecurityCheckTemplateService::MAX_ITEMS; $i++) { $items[] = ['type' => 'check', 'label' => 'Item ' . $i]; } $service = new SecurityCheckTemplateService($repo); $result = $service->saveTechSchema(self::TENANT_ID, 1, $items, 1); $this->assertFalse($result['ok']); $this->assertArrayHasKey('schema', $result['errors']); } public function testUpdateMetaRejectsEmptyName(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $repo->expects($this->never())->method('updateMeta'); $service = new SecurityCheckTemplateService($repo); $result = $service->updateMeta(self::TENANT_ID, 1, '', true, 1); $this->assertFalse($result['ok']); $this->assertArrayHasKey('product_name', $result['errors']); } public function testDecodeSchemaReturnsItems(): void { $repo = $this->createMock(SecurityCheckTemplateRepository::class); $service = new SecurityCheckTemplateService($repo); $decoded = $service->decodeSchema([ 'version' => 5, 'tech_schema_json' => json_encode(['version' => 5, 'items' => [['type' => 'check', 'key' => 'a', 'label' => 'A']]]), ]); $this->assertSame(5, $decoded['version']); $this->assertCount(1, $decoded['items']); } }