This commit is contained in:
2026-11-02 20:29:34 +02:00
parent 340f3bb5fa
commit fcf31e8d48
181 changed files with 33383 additions and 51 deletions
@@ -167,4 +167,72 @@ final class ModuleAssociationReviewTest extends TestCase
}
}
}
public function test_page_batches_shared_dependencies_and_query_count_does_not_grow_per_row(): void {
[$first,$client,$service]=$this->draft();
$data=DB::table('a_service_drafts')->where('id',$first)->value('data');
for($i=0;$i<36;$i++)$this->record('service_drafts',['client_id'=>$client,'data'=>$data]);
for($i=0;$i<250;$i++)$this->record('clients',['name'=>'Outside dependency closure '.$i]);
$calls=0;$audit=0;$active=false;$clientFetches=[];
DB::listen(function($q)use(&$calls,&$audit,&$active,&$clientFetches){if($active){$calls++;if(str_contains($q->sql,'identity_audit'))$audit++;if(str_contains($q->sql,'from "a_clients"'))$clientFetches[]=[$q->sql,$q->bindings];}});
$active=true;$small=$this->getJson('/api/module-records?area=service_drafts&per_page=25');$active=false;
$small->assertOk()->assertJsonCount(25,'records.service_drafts');$smallCalls=$calls;$smallAudit=$audit;
$calls=0;$audit=0;$active=true;$large=$this->getJson('/api/module-records?area=service_drafts&per_page=100');$active=false;
$large->assertOk()->assertJsonPath('pagination.total',37)->assertJsonCount(37,'records.service_drafts');
$this->assertSame($smallCalls,$calls);$this->assertLessThanOrEqual(22,$calls);$this->assertSame(3,$smallAudit);$this->assertSame(3,$audit);
$this->assertCount(2,$clientFetches);
foreach($clientFetches as [$sql,$bindings]){
$this->assertMatchesRegularExpression('/"id" in \(\?\)/',$sql,'Only the referenced client is fetched, not the 250 unrelated clients');
$this->assertSame($client,end($bindings));
}
foreach($large->json('records.service_drafts') as $row){
$this->assertSame([['area'=>'clients','id'=>$client,'version'=>1,'modules'=>[],'revision'=>0],['area'=>'services','id'=>$service,'version'=>1,'modules'=>[],'revision'=>0]],$row['dependencies']);
}
}
public function test_batched_state_preserves_review_only_revision_and_multiple_module_transitions(): void {
$id=$this->record();
IdentityFixture::grant($this->f['manager']->id,$this->f['tenant'],$this->f['company'],'workshop.view',$this->f['unit']);
$review=$this->preview($id,'clients',['operation'=>'remove'])->assertOk()->json('review_token');
$event=$this->confirm($review)->assertOk()->json('event_id');
$this->getJson('/api/module-records?state=pending')->assertOk()->assertJsonPath('records.clients.0.modules',[])->assertJsonPath('records.clients.0.revision',$event);
foreach(['workshop','commercial'] as $module)$this->confirm($this->preview($id,'clients',['module_id'=>$module])->assertOk()->json('review_token'))->assertOk();
$this->getJson('/api/module-records?state=associated')->assertOk()->assertJsonPath('records.clients.0.modules',['commercial','workshop']);
$this->confirm($this->preview($id,'clients',['operation'=>'remove'])->assertOk()->json('review_token'))->assertOk();
$this->getJson('/api/module-records?state=associated')->assertOk()->assertJsonPath('records.clients.0.modules',['workshop']);
$noop=$this->preview($id,'clients',['module_id'=>'workshop'])->assertOk()->json('review_token');
$event=$this->confirm($noop)->assertOk()->json('event_id');$this->confirm($noop)->assertOk()->assertJsonPath('replayed',true);
$this->getJson('/api/module-records?state=associated')->assertOk()->assertJsonPath('records.clients.0.modules',['workshop'])->assertJsonPath('records.clients.0.revision',$event);
$this->confirm($this->preview($id,'clients',['module_id'=>'workshop','operation'=>'remove'])->assertOk()->json('review_token'))->assertOk();
$this->getJson('/api/module-records?state=removed')->assertOk()->assertJsonPath('records.clients.0.modules',[]);
}
public function test_page_cache_never_survives_a_request_and_confirmation_uses_fresh_dependencies(): void {
[$draft,$client]=$this->draft();
$this->getJson('/api/module-records?area=service_drafts')->assertOk()->assertJsonPath('records.service_drafts.0.dependencies_restricted',false);
$token=$this->preview($draft,'service_drafts')->assertOk()->json('review_token');
DB::table('permission_grants')->where('user_id',$this->f['manager']->id)->where('company_id',$this->f['company'])->where('permission','clients.view')->delete();
$this->getJson('/api/module-records?area=service_drafts')->assertOk()->assertJsonPath('records.service_drafts.0.dependencies_restricted',true)->assertJsonPath('records.service_drafts.0.dependencies',[]);
$this->confirm($token)->assertForbidden();
IdentityFixture::grant($this->f['manager']->id,$this->f['tenant'],$this->f['company'],'clients.view');
DB::table('a_clients')->where('id',$client)->update(['version'=>2,'name'=>'Dependency changed after listing']);
$this->getJson('/api/module-records?area=service_drafts')->assertOk()->assertJsonPath('records.service_drafts.0.dependencies.0.version',2);
$this->confirm($token)->assertConflict();
}
public function test_metadata_projection_preserves_reference_order_and_full_content_revalidation(): void {
[$draft,$client,$service]=$this->draft();$second=$this->record('services');
$data=json_decode(DB::table('a_service_drafts')->where('id',$draft)->value('data'),true);
$line=$data['lines'][0];$other=$line;$other['service_id']=$second;
$data['lines']=[$other,$line,$other];$data['description']=str_repeat('DOCUMENT-BODY-',100);
DB::table('a_service_drafts')->where('id',$draft)->update(['version'=>2,'data'=>json_encode($data)]);
$billData=$data;$billData['source_version']=2;
$bill=$this->record('billing_drafts',['client_id'=>$client,'source_id'=>$draft,'data'=>json_encode($billData)]);
$r=$this->getJson('/api/module-records?area=billing_drafts')->assertOk()->assertJsonPath('records.billing_drafts.0.id',$bill);
$services=array_values(array_filter($r->json('records.billing_drafts.0.dependencies'),fn($d)=>$d['area']==='services'));
$this->assertSame([$second,$service],array_column($services,'id'));
$this->assertStringNotContainsString('DOCUMENT-BODY-',$r->getContent());
$this->assertArrayNotHasKey('association_service_ids',$r->json('records.billing_drafts.0'));
$token=$this->preview($bill,'billing_drafts')->assertOk()->json('review_token');
$data['description']='SOURCE-CONTENT-CHANGED-WITH-SAME-REFERENCES';
DB::table('a_service_drafts')->where('id',$draft)->update(['version'=>3,'data'=>json_encode($data)]);
$this->getJson('/api/module-records?area=billing_drafts')->assertOk()->assertJsonPath('records.billing_drafts.0.dependencies.1.version',3);
$this->confirm($token)->assertConflict();
}
}