create(); $schema = FormSchema::factory()->for($organisation)->create(); $submission = FormSubmission::factory()->for($schema, 'schema')->create(); $this->assertSame( $organisation->id, $submission->organisation_id, 'Observer must denormalize organisation_id from schema parent' ); } public function test_event_id_resolves_from_event_owned_schema(): void { $organisation = Organisation::factory()->create(); $event = Event::factory()->for($organisation)->create(); $schema = FormSchema::factory()->for($organisation)->create([ 'owner_type' => 'event', 'owner_id' => $event->id, ]); $submission = FormSubmission::factory()->for($schema, 'schema')->create(); $this->assertSame( $event->id, $submission->event_id, 'Observer must resolve event_id from schema.owner_id when owner_type = event' ); } public function test_event_id_is_null_when_schema_has_no_event_owner_and_no_route(): void { $organisation = Organisation::factory()->create(); $schema = FormSchema::factory()->for($organisation)->create([ 'owner_type' => 'organisation', 'owner_id' => $organisation->id, ]); $submission = FormSubmission::factory()->for($schema, 'schema')->create(); $this->assertNull( $submission->event_id, 'Non-event-owned schema outside an event route yields event_id = null' ); $this->assertSame($organisation->id, $submission->organisation_id); } public function test_event_id_resolves_from_active_route_when_schema_has_no_event_owner(): void { $organisation = Organisation::factory()->create(); $event = Event::factory()->for($organisation)->create(); $schema = FormSchema::factory()->for($organisation)->create([ 'owner_type' => 'organisation', 'owner_id' => $organisation->id, ]); // Simulate a request bound to /events/{event}/... by firing the // observer inside a controller invocation with the route // parameter populated. Route::get( '/_test/events/{event}/submissions', function (Event $event) use ($schema): array { $submission = new FormSubmission(); $submission->form_schema_id = $schema->id; $submission->status = 'draft'; $submission->is_test = false; $submission->save(); return ['id' => $submission->id, 'event_id' => $submission->event_id]; } ); $response = $this->getJson("/_test/events/{$event->id}/submissions"); $response->assertOk(); $this->assertSame( $event->id, $response->json('event_id'), 'Route {event} parameter resolves event_id when schema has no event owner' ); } public function test_organisation_id_is_populated_on_every_create_path(): void { $organisation = Organisation::factory()->create(); $schema = FormSchema::factory()->for($organisation)->create(); // Direct factory $s1 = FormSubmission::factory()->for($schema, 'schema')->create(); // Direct new() + save() $s2 = new FormSubmission(); $s2->form_schema_id = $schema->id; $s2->status = 'draft'; $s2->is_test = false; $s2->save(); // Model::create() $s3 = FormSubmission::create([ 'form_schema_id' => $schema->id, 'status' => 'draft', 'is_test' => false, ]); foreach ([$s1, $s2, $s3] as $submission) { $this->assertSame( $organisation->id, $submission->organisation_id, 'organisation_id must never be NULL — resolved on every create path' ); } } public function test_denormalized_indexes_exist(): void { // Indexes named in the migration — addendum Q2 rapportage-hot. $indexNames = collect(DB::select( "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='form_submissions'" ))->pluck('name')->toArray(); $this->assertContains('fs_org_status_idx', $indexNames); $this->assertContains('fs_event_status_idx', $indexNames); } public function test_organisation_and_event_belongs_to_relationships_resolve(): void { $organisation = Organisation::factory()->create(); $event = Event::factory()->for($organisation)->create(); $schema = FormSchema::factory()->for($organisation)->create([ 'owner_type' => 'event', 'owner_id' => $event->id, ]); $submission = FormSubmission::factory()->for($schema, 'schema')->create(); $this->assertTrue($submission->organisation->is($organisation)); $this->assertTrue($submission->event->is($event)); } }