commit12
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\Support\IdentityFixture;
|
||||
use Tests\TestCase;
|
||||
|
||||
// Transactions only: never migrate/fresh or reset the persistent demonstration.
|
||||
#[\PHPUnit\Framework\Attributes\Group('modular-focused')]
|
||||
final class ModuleNavigationTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
private array $f;
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->f = IdentityFixture::create('-module-'.bin2hex(random_bytes(6)));
|
||||
foreach (['clients.view','services.view'] as $p) IdentityFixture::grant($this->f['reader']->id,$this->f['tenant'],$this->f['company'],$p);
|
||||
IdentityFixture::grant($this->f['reader']->id,$this->f['tenant'],$this->f['company'],'workshop.view',$this->f['unit']);
|
||||
$this->actingAs($this->f['reader'])->withSession(['auth_version'=>$this->f['reader']->auth_version]);
|
||||
}
|
||||
private function select(?string $module, ?int $company=null)
|
||||
{
|
||||
return $this->postJson('/api/context',['tenant_id'=>$this->f['tenant'],'company_id'=>$company??$this->f['company'],'unit_id'=>$this->f['unit'],'module_id'=>$module]);
|
||||
}
|
||||
public function test_explicit_selection_direct_access_and_old_generation(): void
|
||||
{
|
||||
$this->select(null)->assertOk();
|
||||
$this->getJson('/api/a/clients')->assertStatus(409);
|
||||
$token=$this->select('commercial')->assertOk()->json('context.token');
|
||||
$this->withHeader('X-SGF-Context',$token)->getJson('/api/a/clients')->assertOk();
|
||||
$this->getJson('/api/workshop')->assertStatus(409);
|
||||
$next=$this->select('workshop')->assertOk()->json('context.token');
|
||||
$this->assertNotSame($token,$next);
|
||||
$this->withHeader('X-SGF-Context',$token)->getJson('/api/workshop')->assertStatus(409);
|
||||
$this->withHeader('X-SGF-Context',$next)->getJson('/api/a/clients')->assertOk()->assertJsonPath('records',[]);
|
||||
$this->postJson('/api/a/clients',['name'=>'Must not be inserted'])->assertForbidden();
|
||||
$this->assertFalse(DB::table('a_clients')->where('name','Must not be inserted')->exists());
|
||||
}
|
||||
public function test_company_configuration_and_revocation_are_checked_again(): void
|
||||
{
|
||||
$token=$this->select('commercial')->assertOk()->json('context.token');
|
||||
DB::table('permission_grants')->where('user_id',$this->f['reader']->id)->whereIn('permission',['clients.view','services.view'])->delete();
|
||||
$this->withHeader('X-SGF-Context',$token)->getJson('/api/a/clients')->assertForbidden();
|
||||
$this->getJson('/api/session')->assertOk()->assertJsonPath('context.module_id',null);
|
||||
$this->select('commercial')->assertForbidden();
|
||||
$this->select('workshop',$this->f['sibling'])->assertForbidden();
|
||||
config(['modules.companies.'.$this->f['tenant'].':'.$this->f['company']=>['contracted'=>['workshop'],'enabled'=>[]]]);
|
||||
$this->select('workshop')->assertForbidden();
|
||||
}
|
||||
public function test_development_discovery_never_grants_operations_or_applies_in_production(): void
|
||||
{
|
||||
config(['modules.contracted'=>[], 'modules.development_profiles'=>[$this->f['reader']->id.':'.$this->f['tenant'].':'.$this->f['company']]]);
|
||||
$selected=$this->select('purchasing')->assertOk();
|
||||
$this->assertCount(6,$selected->json('context.modules'));
|
||||
$this->withHeader('X-SGF-Context',$selected->json('context.token'))->getJson('/api/supplier-payables')->assertForbidden();
|
||||
app()->detectEnvironment(fn()=>'production');
|
||||
$this->getJson('/api/session')->assertOk()->assertJsonPath('context.modules',[])->assertJsonPath('context.module_id',null);
|
||||
}
|
||||
public function test_failed_selection_preserves_context_and_audit_contains_module_without_token(): void
|
||||
{
|
||||
$selected=$this->select('commercial')->assertOk();
|
||||
$this->select('invented')->assertForbidden();
|
||||
$session=$this->getJson('/api/session')->assertOk();
|
||||
$session->assertJsonPath('context.module_id','commercial')->assertJsonPath('context.token',$selected->json('context.token'));
|
||||
$audit=DB::table('identity_audit')->where('actor_id',$this->f['reader']->id)->where('action','context.selected')->orderByDesc('id')->first();
|
||||
$this->assertSame('commercial',json_decode($audit->details,true)['module_id']);
|
||||
$this->assertStringNotContainsString($selected->json('context.token'),$audit->details);
|
||||
}
|
||||
|
||||
public function test_local_environment_and_copied_profile_cannot_enable_discovery_for_client_database_configuration(): void
|
||||
{
|
||||
$this->select(null)->assertOk();
|
||||
config(['modules.contracted'=>[], 'modules.development_profiles'=>[$this->f['reader']->id.':'.$this->f['tenant'].':'.$this->f['company']]]);
|
||||
$connection=config('database.default');$original=config("database.connections.$connection.database");
|
||||
config(["database.connections.$connection.database"=>'sgf_tempo_dev']);
|
||||
app()->detectEnvironment(fn()=>'local');
|
||||
try { $this->getJson('/api/session')->assertOk()->assertJsonPath('context.modules',[]); }
|
||||
finally { config(["database.connections.$connection.database"=>$original]);app()->detectEnvironment(fn()=>'testing'); }
|
||||
}
|
||||
|
||||
public function test_shared_records_require_explicit_association_and_company_scope(): void
|
||||
{
|
||||
$actor=$this->f['manager'];
|
||||
foreach(['clients.view','clients.create','clients.edit'] as $p)IdentityFixture::grant($actor->id,$this->f['tenant'],$this->f['company'],$p);
|
||||
IdentityFixture::grant($actor->id,$this->f['tenant'],$this->f['company'],'workshop.view',$this->f['unit']);
|
||||
$this->actingAs($actor)->withSession(['auth_version'=>$actor->auth_version]);
|
||||
$token=$this->select('commercial')->assertOk()->json('context.token');
|
||||
$this->withHeaders(['X-SGF-Context'=>$token,'Idempotency-Key'=>'module-client-proof-001']);
|
||||
$id=$this->postJson('/api/a/clients',['name'=>'Module proof client'])->assertCreated()->json('id');
|
||||
$this->getJson('/api/a/clients')->assertOk()->assertJsonPath('records.0.id',$id);
|
||||
$token=$this->select('workshop')->assertOk()->json('context.token');
|
||||
$this->withHeader('X-SGF-Context',$token)->getJson('/api/a/clients')->assertOk()->assertJsonPath('records',[]);
|
||||
$this->getJson('/api/a/clients/'.$id)->assertForbidden();
|
||||
$this->getJson('/api/module-records')->assertOk()->assertJsonPath('records.clients.0.id',$id);
|
||||
$this->postJson('/api/module-records',['area'=>'clients','id'=>$id,'version'=>1,'module_id'=>'workshop','reason'=>'Explicit synthetic association'])->assertOk();
|
||||
$this->getJson('/api/a/clients/'.$id)->assertOk()->assertJsonPath('name','Module proof client');
|
||||
$this->assertSame(1,DB::table('a_clients')->where('id',$id)->value('version'));
|
||||
$this->assertSame(2,DB::table('identity_audit')->where('action','module.record.associated')->where('company_id',$this->f['company'])->count());
|
||||
$this->postJson('/api/module-records',['area'=>'clients','id'=>$id,'version'=>1,'module_id'=>'workshop','reason'=>'Correct association without rewriting original','operation'=>'remove'])->assertOk();
|
||||
$this->getJson('/api/a/clients/'.$id)->assertForbidden();
|
||||
$this->getJson('/api/module-records')->assertOk()->assertJsonPath('records.clients.0.modules',['commercial']);
|
||||
$this->assertSame(1,DB::table('a_clients')->where('id',$id)->value('version'));
|
||||
$this->postJson('/api/module-records',['area'=>'clients','id'=>$id,'version'=>1,'module_id'=>'workshop','reason'=>'Re-association explicitly reviewed'])->assertOk();
|
||||
$this->getJson('/api/a/clients/'.$id)->assertOk();
|
||||
IdentityFixture::grant($actor->id,$this->f['tenant'],$this->f['sibling'],'clients.view');
|
||||
$token=$this->postJson('/api/context',['tenant_id'=>$this->f['tenant'],'company_id'=>$this->f['sibling'],'unit_id'=>$this->f['siblingUnit'],'module_id'=>'commercial'])->assertOk()->json('context.token');
|
||||
$this->withHeader('X-SGF-Context',$token)->getJson('/api/a/clients/'.$id)->assertForbidden();
|
||||
$this->getJson('/api/a/clients')->assertOk()->assertJsonPath('records',[]);
|
||||
}
|
||||
|
||||
public function test_unassociated_legacy_records_remain_in_explicit_administrative_inventory(): void
|
||||
{
|
||||
$id=DB::table('a_clients')->insertGetId(['tenant_id'=>$this->f['tenant'],'company_id'=>$this->f['company'],'unit_id'=>null,'name'=>'Existing unassociated record','version'=>1,'data'=>'{}','created_at'=>now(),'updated_at'=>now()]);
|
||||
$token=$this->select('commercial')->assertOk()->json('context.token');
|
||||
$this->withHeader('X-SGF-Context',$token)->getJson('/api/a/clients')->assertOk()->assertJsonPath('records',[])->assertJsonStructure(['scope_notice']);
|
||||
$this->getJson('/api/a/clients/'.$id)->assertForbidden();
|
||||
$this->getJson('/api/module-records')->assertForbidden();
|
||||
IdentityFixture::grant($this->f['reader']->id,$this->f['tenant'],$this->f['company'],'access.manage');
|
||||
$this->getJson('/api/module-records')->assertOk()->assertJsonPath('records.clients.0.id',$id)->assertJsonPath('records.clients.0.modules',[]);
|
||||
$this->postJson('/api/module-records',['area'=>'clients','id'=>$id,'version'=>1,'module_id'=>'commercial','reason'=>'Cannot assign without edit permission'])->assertForbidden();
|
||||
$this->assertSame('Existing unassociated record',DB::table('a_clients')->where('id',$id)->value('name'));
|
||||
}
|
||||
|
||||
public function test_draft_creation_preserves_source_history_and_does_not_share_dependencies_implicitly(): void
|
||||
{
|
||||
$actor=$this->f['manager'];
|
||||
foreach(['clients','services','service_drafts','billing_drafts'] as $area){
|
||||
foreach(['view','create','edit'] as $action)IdentityFixture::grant($actor->id,$this->f['tenant'],$this->f['company'],"$area.$action",str_ends_with($area,'_drafts')?$this->f['unit']:null);
|
||||
}
|
||||
IdentityFixture::grant($actor->id,$this->f['tenant'],$this->f['company'],'workshop.view',$this->f['unit']);
|
||||
$this->actingAs($actor)->withSession(['auth_version'=>$actor->auth_version]);
|
||||
$token=$this->select('commercial')->assertOk()->json('context.token');
|
||||
$write=function($area,$body)use($token){return $this->withHeaders(['X-SGF-Context'=>$token,'Idempotency-Key'=>(string)\Illuminate\Support\Str::uuid()])->postJson('/api/a/'.$area,$body)->assertCreated()->json('id');};
|
||||
$client=$write('clients',['name'=>'Synthetic modular client']);
|
||||
$service=$write('services',['name'=>'Synthetic modular service']);
|
||||
$draft=$write('service_drafts',['client_id'=>$client,'currency'=>'MZN','lines'=>[['service_id'=>$service,'quantity'=>'2','unit_price'=>'75']]]);
|
||||
$bill=$write('billing_drafts',['source_id'=>$draft]);
|
||||
$original=$this->getJson('/api/a/billing_drafts/'.$bill)->assertOk()->json();
|
||||
$this->assertSame($draft,$original['source_id']);
|
||||
$token=$this->select('workshop')->assertOk()->json('context.token');
|
||||
$this->withHeader('X-SGF-Context',$token)->postJson('/api/module-records',['area'=>'billing_drafts','id'=>$bill,'version'=>99,'module_id'=>'workshop','reason'=>'Reject stale review'])->assertStatus(409);
|
||||
$this->postJson('/api/module-records',['area'=>'billing_drafts','id'=>$bill,'version'=>1,'module_id'=>'workshop','reason'=>'Explicitly share this preserved draft'])->assertOk();
|
||||
$record=$this->getJson('/api/a/billing_drafts/'.$bill)->assertOk()->json();
|
||||
$this->assertSame($original['data'],$record['data']);
|
||||
$this->assertSame($original['history'],$record['history']);
|
||||
$this->assertSame($draft,$record['source_id']);
|
||||
$this->getJson('/api/a/service_drafts/'.$draft)->assertForbidden();
|
||||
$this->getJson('/api/a/clients/'.$client)->assertForbidden();
|
||||
$this->getJson('/api/a/services/'.$service)->assertForbidden();
|
||||
$this->assertSame(1,DB::table('a_billing_drafts')->where('id',$bill)->value('version'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user