diff --git a/database/migrations/2025_10_06_180000_alter_document_templates_slug_unique.php b/database/migrations/2025_10_06_180000_alter_document_templates_slug_unique.php new file mode 100644 index 0000000..a86f173 --- /dev/null +++ b/database/migrations/2025_10_06_180000_alter_document_templates_slug_unique.php @@ -0,0 +1,44 @@ +dropUnique('document_templates_slug_unique'); + } catch (Throwable $e) { + // Ignore if already dropped / not present (SQLite memory or prior manual change) + } + // Add composite unique to allow multiple versions per slug while preventing duplicate version numbers + $table->unique(['slug', 'version'], 'document_templates_slug_version_unique'); + // Optional plain index on slug alone (not unique) to speed lookups by slug + $table->index('slug', 'document_templates_slug_index'); + }); + } + + public function down(): void + { + Schema::table('document_templates', function (Blueprint $table) { + // Drop composite + plain indices + try { + $table->dropIndex('document_templates_slug_index'); + } catch (Throwable $e) { + } + try { + $table->dropUnique('document_templates_slug_version_unique'); + } catch (Throwable $e) { + } + // Restore original simple unique on slug + try { + $table->unique('slug', 'document_templates_slug_unique'); + } catch (Throwable $e) { + } + }); + } +}; diff --git a/tests/Feature/DocumentTemplateVersioningTest.php b/tests/Feature/DocumentTemplateVersioningTest.php new file mode 100644 index 0000000..75e4783 --- /dev/null +++ b/tests/Feature/DocumentTemplateVersioningTest.php @@ -0,0 +1,59 @@ +create(); + $role = Role::firstOrCreate(['slug' => 'admin'], ['name' => 'Admin']); + $user->roles()->sync([$role->id]); + $this->actingAs($user); + + $docxV1 = $this->fakeDocxWithBody('{{contract.reference}}'); + $this->post(route('admin.document-templates.store'), [ + 'name' => 'Povzetek pogodbe', + 'slug' => 'contract-summary', + 'file' => $docxV1, + ])->assertRedirect(); + + $this->assertDatabaseHas('document_templates', [ + 'slug' => 'contract-summary', + 'version' => 1, + ]); + + $docxV2 = $this->fakeDocxWithBody('{{contract.reference}} {{generation.date}}'); + $this->post(route('admin.document-templates.store'), [ + 'name' => 'Povzetek pogodbe', + 'slug' => 'contract-summary', + 'file' => $docxV2, + ])->assertRedirect(); + + $this->assertDatabaseHas('document_templates', [ + 'slug' => 'contract-summary', + 'version' => 2, + ]); + + $this->assertEquals(2, \App\Models\DocumentTemplate::where('slug', 'contract-summary')->count()); + } + + private function fakeDocxWithBody(string $body): UploadedFile + { + $tmp = tempnam(sys_get_temp_dir(), 'docx'); + $zip = new \ZipArchive; + $zip->open($tmp, \ZipArchive::OVERWRITE); + $zip->addFromString('[Content_Types].xml', ''); + $zip->addFromString('word/document.xml', ''.$body.''); + $zip->close(); + + return UploadedFile::fake()->createWithContent('template.docx', file_get_contents($tmp)); + } +}