Commit workspace changes

This commit is contained in:
2026-04-05 19:42:33 +02:00
parent 148a3bbe43
commit 08ad757bcb
312 changed files with 35149 additions and 399 deletions

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
use App\Models\Artwork;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
uses(RefreshDatabase::class);
it('passes when every artwork has a matching user', function (): void {
Artwork::factory()->count(3)->create();
$code = Artisan::call('artworks:check-user-refs', [
'--chunk' => 2,
'--show-missing' => 5,
]);
$output = Artisan::output();
file_put_contents(storage_path('logs/check-artwork-user-refs-test-output.log'), $output);
expect($code)->toBe(0)
->and($output)->toContain('Checked 3 artworks: 3 valid, 0 missing user references, 0 null user_id values.')
->and($output)->toContain('No missing user references found in artworks.user_id.');
});
it('fails and reports orphaned artwork user references', function (): void {
$validArtwork = Artwork::factory()->create();
$orphanedArtwork = Artwork::factory()->create();
DB::table('artworks')
->where('id', $orphanedArtwork->id)
->update(['user_id' => 999999]);
$code = Artisan::call('artworks:check-user-refs', [
'--chunk' => 1,
'--show-missing' => 5,
]);
$output = Artisan::output();
expect($validArtwork->fresh())->not->toBeNull()
->and($code)->toBe(1)
->and($output)->toContain('Checked 2 artworks: 1 valid, 1 missing user references, 0 null user_id values.')
->and($output)->toContain('Found artworks with missing user references.')
->and($output)->toContain((string) $orphanedArtwork->id)
->and($output)->toContain('999999');
});
it('can copy missing referenced users from the legacy users table by the same id', function (): void {
$legacyUserId = 777;
$artwork = Artwork::factory()->create();
DB::table('artworks')
->where('id', $artwork->id)
->update(['user_id' => $legacyUserId]);
config()->set('database.connections.legacy', config('database.connections.' . config('database.default')));
DB::purge('legacy');
Schema::connection('legacy')->dropIfExists('legacy_users');
Schema::connection('legacy')->create('legacy_users', function (Blueprint $table): void {
$table->unsignedBigInteger('user_id')->primary();
$table->string('uname')->nullable();
$table->string('real_name')->nullable();
$table->string('email')->nullable();
$table->unsignedTinyInteger('active')->default(1);
$table->timestamp('joinDate')->nullable();
$table->timestamp('LastVisit')->nullable();
$table->string('country')->nullable();
$table->string('country_code', 2)->nullable();
$table->string('web')->nullable();
$table->text('about_me')->nullable();
$table->text('description')->nullable();
$table->string('gender', 16)->nullable();
});
DB::connection('legacy')->table('legacy_users')->insert([
'user_id' => $legacyUserId,
'uname' => 'legacy_artist',
'real_name' => 'Legacy Artist',
'email' => 'legacy.artist@example.test',
'active' => 1,
'joinDate' => '2020-01-02 03:04:05',
'LastVisit' => '2025-01-05 06:07:08',
'country' => 'Finland',
'country_code' => 'FI',
'web' => 'legacy.example.test',
'about_me' => 'Imported from legacy.',
'gender' => 'F',
]);
$code = Artisan::call('artworks:check-user-refs', [
'--chunk' => 1,
'--show-missing' => 5,
'--copy-missing-from-legacy' => true,
'--legacy-users-table' => 'legacy_users',
]);
$output = Artisan::output();
file_put_contents(storage_path('logs/check-artwork-user-refs-copy-test-output.log'), $output);
expect($code)->toBe(0)
->and(DB::table('users')->where('id', $legacyUserId)->exists())->toBeTrue()
->and(DB::table('user_profiles')->where('user_id', $legacyUserId)->value('country_code'))->toBe('FI')
->and($output)->toContain('[copied] imported legacy user #777 username=@legacy_artist name="Legacy Artist" email=<legacy.artist@example.test>')
->and($output)->toContain('Checked 1 artworks: 1 valid, 0 missing user references, 0 null user_id values.')
->and($output)->toContain('Legacy copy summary: requested 1 users, copied 1, would copy 0, conflicts 0, not found in legacy 0, errors 0.')
->and($output)->toContain('Copied or would-copy user ids: 777');
Schema::connection('legacy')->dropIfExists('legacy_users');
});
it('shows dry run copy details for legacy imports', function (): void {
$legacyUserId = 778;
$artwork = Artwork::factory()->create();
DB::table('artworks')
->where('id', $artwork->id)
->update(['user_id' => $legacyUserId]);
config()->set('database.connections.legacy', config('database.connections.' . config('database.default')));
DB::purge('legacy');
Schema::connection('legacy')->dropIfExists('legacy_users');
Schema::connection('legacy')->create('legacy_users', function (Blueprint $table): void {
$table->unsignedBigInteger('user_id')->primary();
$table->string('uname')->nullable();
$table->string('real_name')->nullable();
$table->string('email')->nullable();
$table->unsignedTinyInteger('active')->default(1);
$table->timestamp('joinDate')->nullable();
$table->timestamp('LastVisit')->nullable();
$table->string('country')->nullable();
$table->string('country_code', 2)->nullable();
$table->string('web')->nullable();
$table->text('about_me')->nullable();
$table->text('description')->nullable();
$table->string('gender', 16)->nullable();
});
DB::connection('legacy')->table('legacy_users')->insert([
'user_id' => $legacyUserId,
'uname' => 'legacy_preview',
'real_name' => 'Legacy Preview',
'email' => 'legacy.preview@example.test',
'active' => 1,
]);
$code = Artisan::call('artworks:check-user-refs', [
'--chunk' => 1,
'--show-missing' => 5,
'--copy-missing-from-legacy' => true,
'--dry-run-copy' => true,
'--legacy-users-table' => 'legacy_users',
]);
$output = Artisan::output();
expect($code)->toBe(1)
->and(DB::table('users')->where('id', $legacyUserId)->exists())->toBeFalse()
->and($output)->toContain('[dry-run] would import legacy user #778 username=@legacy_preview name="Legacy Preview" email=<legacy.preview@example.test>')
->and($output)->toContain('Legacy copy summary: requested 1 users, copied 0, would copy 1, conflicts 0, not found in legacy 0, errors 0.');
Schema::connection('legacy')->dropIfExists('legacy_users');
});

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use cPad\Plugins\News\Models\NewsArticle;
use cPad\Plugins\News\Models\NewsCategory;
uses(RefreshDatabase::class);
it('publishes scheduled news articles whose publish time has passed', function (): void {
$author = User::factory()->create();
$category = NewsCategory::query()->create([
'name' => 'Announcements',
'slug' => 'announcements',
'description' => 'Announcement category',
'position' => 0,
'is_active' => true,
]);
$dueArticle = NewsArticle::query()->create([
'title' => 'Due scheduled article',
'slug' => 'due-scheduled-article',
'excerpt' => 'Due now.',
'content' => 'Body',
'author_id' => $author->id,
'category_id' => $category->id,
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'status' => 'scheduled',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_SCHEDULED,
'published_at' => now()->subMinute(),
]);
$futureArticle = NewsArticle::query()->create([
'title' => 'Future scheduled article',
'slug' => 'future-scheduled-article',
'excerpt' => 'Not due yet.',
'content' => 'Body',
'author_id' => $author->id,
'category_id' => $category->id,
'type' => NewsArticle::TYPE_ANNOUNCEMENT,
'status' => 'scheduled',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_SCHEDULED,
'published_at' => now()->addHour(),
]);
$this->artisan('news:publish-scheduled')
->expectsOutput(sprintf('Published News article #%d: "%s"', $dueArticle->id, $dueArticle->title))
->expectsOutput('Done. Published: 1, Errors: 0.')
->assertSuccessful();
expect($dueArticle->fresh())
->editorial_status->toBe(NewsArticle::EDITORIAL_STATUS_PUBLISHED)
->status->toBe('published')
->and($futureArticle->fresh())
->editorial_status->toBe(NewsArticle::EDITORIAL_STATUS_SCHEDULED)
->status->toBe('scheduled');
});
it('supports dry run for scheduled news publishing', function (): void {
$author = User::factory()->create();
$category = NewsCategory::query()->create([
'name' => 'Platform',
'slug' => 'platform',
'description' => 'Platform category',
'position' => 0,
'is_active' => true,
]);
$article = NewsArticle::query()->create([
'title' => 'Dry run article',
'slug' => 'dry-run-article',
'excerpt' => 'Due but not published in dry run.',
'content' => 'Body',
'author_id' => $author->id,
'category_id' => $category->id,
'type' => NewsArticle::TYPE_PLATFORM_UPDATE,
'status' => 'scheduled',
'editorial_status' => NewsArticle::EDITORIAL_STATUS_SCHEDULED,
'published_at' => now()->subMinute(),
]);
$this->artisan('news:publish-scheduled', ['--dry-run' => true])
->expectsOutput(sprintf('[dry-run] Would publish News article #%d: "%s"', $article->id, $article->title))
->assertSuccessful();
expect($article->fresh())
->editorial_status->toBe(NewsArticle::EDITORIAL_STATUS_SCHEDULED)
->status->toBe('scheduled');
});