test: add PromptVersion model tests #45
2 changed files with 344 additions and 0 deletions
65
Migrations/0001_01_01_000004_create_prompt_tables.php
Normal file
65
Migrations/0001_01_01_000004_create_prompt_tables.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Create prompts and prompt_versions tables.
|
||||
*
|
||||
* Guarded with hasTable() so this migration is idempotent and
|
||||
* can coexist with the consolidated app-level migration.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::disableForeignKeyConstraints();
|
||||
|
||||
if (! Schema::hasTable('prompts')) {
|
||||
Schema::create('prompts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('category')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->text('system_prompt')->nullable();
|
||||
$table->text('user_template')->nullable();
|
||||
$table->json('variables')->nullable();
|
||||
$table->string('model')->nullable();
|
||||
$table->json('model_config')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('category');
|
||||
$table->index('is_active');
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('prompt_versions')) {
|
||||
Schema::create('prompt_versions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('prompt_id')->constrained()->cascadeOnDelete();
|
||||
$table->unsignedInteger('version');
|
||||
$table->text('system_prompt')->nullable();
|
||||
$table->text('user_template')->nullable();
|
||||
$table->json('variables')->nullable();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['prompt_id', 'version']);
|
||||
});
|
||||
}
|
||||
|
||||
Schema::enableForeignKeyConstraints();
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::disableForeignKeyConstraints();
|
||||
Schema::dropIfExists('prompt_versions');
|
||||
Schema::dropIfExists('prompts');
|
||||
Schema::enableForeignKeyConstraints();
|
||||
}
|
||||
};
|
||||
279
tests/Feature/PromptVersionTest.php
Normal file
279
tests/Feature/PromptVersionTest.php
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Core\Mod\Agentic\Models\Prompt;
|
||||
use Core\Mod\Agentic\Models\PromptVersion;
|
||||
use Core\Tenant\Models\User;
|
||||
|
||||
// =========================================================================
|
||||
// Version Creation Tests
|
||||
// =========================================================================
|
||||
|
||||
describe('version creation', function () {
|
||||
it('can be created with required attributes', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'system_prompt' => 'You are a helpful assistant.',
|
||||
'user_template' => 'Answer this: {{{question}}}',
|
||||
'variables' => ['question'],
|
||||
]);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
'system_prompt' => 'You are a helpful assistant.',
|
||||
'user_template' => 'Answer this: {{{question}}}',
|
||||
'variables' => ['question'],
|
||||
]);
|
||||
|
||||
expect($version->id)->not->toBeNull()
|
||||
->and($version->version)->toBe(1)
|
||||
->and($version->prompt_id)->toBe($prompt->id);
|
||||
});
|
||||
|
||||
it('casts variables as array', function () {
|
||||
$prompt = Prompt::create(['name' => 'Test Prompt']);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
'variables' => ['topic', 'tone'],
|
||||
]);
|
||||
|
||||
expect($version->variables)
|
||||
->toBeArray()
|
||||
->toBe(['topic', 'tone']);
|
||||
});
|
||||
|
||||
it('casts version as integer', function () {
|
||||
$prompt = Prompt::create(['name' => 'Test Prompt']);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 3,
|
||||
]);
|
||||
|
||||
expect($version->version)->toBeInt()->toBe(3);
|
||||
});
|
||||
|
||||
it('can be created without optional fields', function () {
|
||||
$prompt = Prompt::create(['name' => 'Minimal Prompt']);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
]);
|
||||
|
||||
expect($version->id)->not->toBeNull()
|
||||
->and($version->system_prompt)->toBeNull()
|
||||
->and($version->user_template)->toBeNull()
|
||||
->and($version->created_by)->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// Relationship Tests
|
||||
// =========================================================================
|
||||
|
||||
describe('relationships', function () {
|
||||
it('belongs to a prompt', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Parent Prompt',
|
||||
'system_prompt' => 'System text.',
|
||||
]);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
]);
|
||||
|
||||
expect($version->prompt)->toBeInstanceOf(Prompt::class)
|
||||
->and($version->prompt->id)->toBe($prompt->id)
|
||||
->and($version->prompt->name)->toBe('Parent Prompt');
|
||||
});
|
||||
|
||||
it('belongs to a creator user', function () {
|
||||
$user = User::factory()->create();
|
||||
$prompt = Prompt::create(['name' => 'Test Prompt']);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
|
||||
expect($version->creator)->toBeInstanceOf(User::class)
|
||||
->and($version->creator->id)->toBe($user->id);
|
||||
});
|
||||
|
||||
it('has null creator when created_by is null', function () {
|
||||
$prompt = Prompt::create(['name' => 'Test Prompt']);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
]);
|
||||
|
||||
expect($version->creator)->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// Restore Method Tests
|
||||
// =========================================================================
|
||||
|
||||
describe('restore', function () {
|
||||
it('restores system_prompt and user_template to the parent prompt', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'system_prompt' => 'Original system prompt.',
|
||||
'user_template' => 'Original template.',
|
||||
]);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
'system_prompt' => 'Versioned system prompt.',
|
||||
'user_template' => 'Versioned template.',
|
||||
]);
|
||||
|
||||
$prompt->update([
|
||||
'system_prompt' => 'Newer system prompt.',
|
||||
'user_template' => 'Newer template.',
|
||||
]);
|
||||
|
||||
$version->restore();
|
||||
|
||||
$fresh = $prompt->fresh();
|
||||
expect($fresh->system_prompt)->toBe('Versioned system prompt.')
|
||||
->and($fresh->user_template)->toBe('Versioned template.');
|
||||
});
|
||||
|
||||
it('restores variables to the parent prompt', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'variables' => ['topic'],
|
||||
]);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
'variables' => ['topic', 'tone'],
|
||||
]);
|
||||
|
||||
$prompt->update(['variables' => ['topic', 'tone', 'length']]);
|
||||
|
||||
$version->restore();
|
||||
|
||||
expect($prompt->fresh()->variables)->toBe(['topic', 'tone']);
|
||||
});
|
||||
|
||||
it('returns the parent prompt instance after restore', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'system_prompt' => 'Old.',
|
||||
]);
|
||||
|
||||
$version = PromptVersion::create([
|
||||
'prompt_id' => $prompt->id,
|
||||
'version' => 1,
|
||||
'system_prompt' => 'Versioned.',
|
||||
]);
|
||||
|
||||
$result = $version->restore();
|
||||
|
||||
expect($result)->toBeInstanceOf(Prompt::class)
|
||||
->and($result->id)->toBe($prompt->id);
|
||||
});
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// Version History Tests
|
||||
// =========================================================================
|
||||
|
||||
describe('version history', function () {
|
||||
it('prompt tracks multiple versions in descending order', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Evolving Prompt',
|
||||
'system_prompt' => 'v1.',
|
||||
]);
|
||||
|
||||
PromptVersion::create(['prompt_id' => $prompt->id, 'version' => 1, 'system_prompt' => 'v1.']);
|
||||
PromptVersion::create(['prompt_id' => $prompt->id, 'version' => 2, 'system_prompt' => 'v2.']);
|
||||
PromptVersion::create(['prompt_id' => $prompt->id, 'version' => 3, 'system_prompt' => 'v3.']);
|
||||
|
||||
$versions = $prompt->versions()->get();
|
||||
|
||||
expect($versions)->toHaveCount(3)
|
||||
->and($versions->first()->version)->toBe(3)
|
||||
->and($versions->last()->version)->toBe(1);
|
||||
});
|
||||
|
||||
it('createVersion snapshots current prompt state', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'system_prompt' => 'Original system prompt.',
|
||||
'user_template' => 'Original template.',
|
||||
'variables' => ['topic'],
|
||||
]);
|
||||
|
||||
$version = $prompt->createVersion();
|
||||
|
||||
expect($version)->toBeInstanceOf(PromptVersion::class)
|
||||
->and($version->version)->toBe(1)
|
||||
->and($version->system_prompt)->toBe('Original system prompt.')
|
||||
->and($version->user_template)->toBe('Original template.')
|
||||
->and($version->variables)->toBe(['topic']);
|
||||
});
|
||||
|
||||
it('createVersion increments version number', function () {
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'system_prompt' => 'v1.',
|
||||
]);
|
||||
|
||||
$v1 = $prompt->createVersion();
|
||||
$prompt->update(['system_prompt' => 'v2.']);
|
||||
$v2 = $prompt->createVersion();
|
||||
|
||||
expect($v1->version)->toBe(1)
|
||||
->and($v2->version)->toBe(2);
|
||||
});
|
||||
|
||||
it('createVersion records the creator user id', function () {
|
||||
$user = User::factory()->create();
|
||||
$prompt = Prompt::create([
|
||||
'name' => 'Test Prompt',
|
||||
'system_prompt' => 'System text.',
|
||||
]);
|
||||
|
||||
$version = $prompt->createVersion($user->id);
|
||||
|
||||
expect($version->created_by)->toBe($user->id);
|
||||
});
|
||||
|
||||
it('versions are scoped to their parent prompt', function () {
|
||||
$promptA = Prompt::create(['name' => 'Prompt A']);
|
||||
$promptB = Prompt::create(['name' => 'Prompt B']);
|
||||
|
||||
PromptVersion::create(['prompt_id' => $promptA->id, 'version' => 1]);
|
||||
PromptVersion::create(['prompt_id' => $promptA->id, 'version' => 2]);
|
||||
PromptVersion::create(['prompt_id' => $promptB->id, 'version' => 1]);
|
||||
|
||||
expect($promptA->versions()->count())->toBe(2)
|
||||
->and($promptB->versions()->count())->toBe(1);
|
||||
});
|
||||
|
||||
it('deleting prompt cascades to versions', function () {
|
||||
$prompt = Prompt::create(['name' => 'Test Prompt']);
|
||||
|
||||
PromptVersion::create(['prompt_id' => $prompt->id, 'version' => 1]);
|
||||
PromptVersion::create(['prompt_id' => $prompt->id, 'version' => 2]);
|
||||
|
||||
$promptId = $prompt->id;
|
||||
$prompt->delete();
|
||||
|
||||
expect(PromptVersion::where('prompt_id', $promptId)->count())->toBe(0);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue