149 lines
10 KiB
PHP
149 lines
10 KiB
PHP
<?php
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\AuditLog;
|
|
use App\Models\PeopleProcess;
|
|
use App\Models\Requisition;
|
|
use App\Models\User;
|
|
use App\Services\OrganizationAccess;
|
|
use App\Services\PeopleContractHandoff;
|
|
use App\Services\WorkContext;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
require_once __DIR__.'/../Support/ReceiptFixture.php';
|
|
|
|
beforeEach(function () {
|
|
$this->f = receiptFixture();
|
|
$this->actor = $this->f['issuer'];
|
|
$this->actor->forceFill(['purchasing_role' => PurchasingRole::Admin])->save();
|
|
$this->f['tenant']->update(['rules_provisional' => false, 'work_context_policy' => ['revision' => 1, 'enabled' => array_keys(WorkContext::OPTIONS)]]);
|
|
config(['work_context.entitlements.'.$this->actor->tenant_id => array_keys(WorkContext::OPTIONS)]);
|
|
$this->actingAs($this->actor);
|
|
});
|
|
|
|
test('selection intersects product configuration and permission without granting functions', function () {
|
|
$before = app(OrganizationAccess::class)->permissions($this->actor);
|
|
config(['work_context.entitlements.'.$this->actor->tenant_id => ['procurement.internal']]);
|
|
$this->post('/contexto', ['area' => 'people', 'option' => 'people.internal'])->assertForbidden();
|
|
$this->post('/contexto', ['area' => 'procurement'])->assertRedirect('/dashboard');
|
|
expect(session('work_context.key'))->toBe('procurement.internal')
|
|
->and(app(OrganizationAccess::class)->permissions($this->actor))->toBe($before);
|
|
$this->get('/requisicoes')->assertOk();
|
|
$this->get('/pessoas')->assertForbidden();
|
|
$this->get('/pessoas/relatorios')->assertForbidden();
|
|
$this->get('/pessoas/relatorios.csv')->assertForbidden();
|
|
$this->post('/pessoas', [])->assertForbidden();
|
|
});
|
|
|
|
test('two choices require explicit option and none exposes no business data on dashboard', function () {
|
|
$this->post('/contexto', ['area' => 'procurement'])->assertForbidden();
|
|
$this->get('/dashboard')->assertOk()->assertInertia(fn (Assert $page) => $page->component('work-context')->missing('stats')->missing('recent')->where('context.active', null));
|
|
config(['work_context.entitlements.'.$this->actor->tenant_id => []]);
|
|
$this->get('/dashboard')->assertInertia(fn (Assert $page) => $page->where('context.available', []));
|
|
$this->get('/requisicoes')->assertForbidden();
|
|
});
|
|
|
|
test('context changes invalidate old area operations and do not change submitted records', function () {
|
|
$before = $this->f['r']->fresh()->toArray();
|
|
$this->post('/contexto', ['area' => 'procurement', 'option' => 'procurement.internal'])->assertRedirect();
|
|
$this->post('/contexto', ['area' => 'people', 'option' => 'people.internal'])->assertRedirect();
|
|
$this->get('/requisicoes/'.$this->f['r']->id)->assertNotFound();
|
|
$this->get('/requisicoes/'.$this->f['r']->id.'/relatorio')->assertNotFound();
|
|
$this->post('/requisicoes/'.$this->f['r']->id.'/submeter')->assertNotFound();
|
|
expect(Requisition::withoutGlobalScope('work_context')->findOrFail($this->f['r']->id)->toArray())->toBe($before);
|
|
});
|
|
|
|
test('commercial withdrawal invalidates active context and pending options cannot reuse internal workflows', function () {
|
|
$this->post('/contexto', ['area' => 'procurement', 'option' => 'procurement.internal'])->assertRedirect();
|
|
config(['work_context.entitlements.'.$this->actor->tenant_id => ['procurement.sales']]);
|
|
$this->get('/requisicoes')->assertForbidden();
|
|
$this->post('/contexto', ['area' => 'procurement', 'option' => 'procurement.sales'])->assertRedirect();
|
|
$this->get('/requisicoes')->assertRedirect('/dashboard');
|
|
$this->post('/requisicoes', [])->assertStatus(409);
|
|
});
|
|
|
|
test('people reports in a pending modality return to the context panel without exposing internal data', function () {
|
|
$this->post('/contexto', ['area' => 'people', 'option' => 'people.third_party'])->assertRedirect('/dashboard');
|
|
$this->get('/pessoas/relatorios')->assertRedirect('/dashboard');
|
|
$this->get('/pessoas/relatorios.csv')->assertRedirect('/dashboard');
|
|
$this->get('/dashboard')->assertOk()->assertInertia(fn (Assert $page) => $page
|
|
->component('work-context')->where('context.active', 'people.third_party')->missing('processes'));
|
|
$this->post('/pessoas', [])->assertStatus(409);
|
|
$this->post('/contexto', ['area' => 'people', 'option' => 'people.internal'])->assertRedirect('/dashboard');
|
|
$this->get('/pessoas/relatorios')->assertOk()->assertInertia(fn (Assert $page) => $page->component('people/reports'));
|
|
});
|
|
|
|
test('context is bound to account and company and rejects area mismatches', function () {
|
|
$this->post('/contexto', ['area' => 'procurement', 'option' => 'people.internal'])->assertForbidden();
|
|
$this->post('/contexto', ['area' => 'procurement', 'option' => 'procurement.internal'])->assertRedirect();
|
|
$other = User::factory()->create(['tenant_id' => $this->actor->tenant_id, 'department_id' => $this->actor->department_id, 'purchasing_role' => PurchasingRole::Admin]);
|
|
$this->actingAs($other)->get('/requisicoes')->assertForbidden();
|
|
expect(AuditLog::query()->where('action', 'work_context.selected')->count())->toBe(1);
|
|
});
|
|
|
|
test('area profiles do not combine permissions from other contexts and expired membership grants nothing', function () {
|
|
$tenant = $this->actor->tenant_id;
|
|
DB::table('organization_access')->insert(['tenant_id' => $tenant, 'active' => true, 'version' => 1, 'created_at' => now(), 'updated_at' => now()]);
|
|
foreach ([['procurement.internal', ['requester']], ['people.internal', ['admin']]] as [$key, $permissions]) {
|
|
$profile = DB::table('organization_profiles')->insertGetId(['tenant_id' => $tenant, 'name' => $key, 'permissions' => json_encode($permissions), 'work_contexts' => json_encode([$key]), 'version' => 1, 'created_at' => now(), 'updated_at' => now()]);
|
|
DB::table('organization_memberships')->insert(['tenant_id' => $tenant, 'user_id' => $this->actor->id, 'profile_id' => $profile, 'maintenance' => false, 'created_at' => now(), 'updated_at' => now()]);
|
|
}
|
|
$this->post('/contexto', ['area' => 'procurement'])->assertRedirect();
|
|
$this->get('/dashboard')->assertInertia(fn (Assert $page) => $page->where('context.permissions', ['requester']));
|
|
$this->get('/requisicoes')->assertInertia(fn (Assert $page) => $page->where('organizationAccess.permissions', ['requester']));
|
|
DB::table('organization_memberships')->where('tenant_id', $tenant)->update(['expires_at' => now()->subMinute()]);
|
|
$this->get('/requisicoes')->assertForbidden();
|
|
});
|
|
|
|
test('product configuration cannot enable unpurchased options and changes are versioned audited', function () {
|
|
config(['work_context.entitlements.'.$this->actor->tenant_id => ['procurement.internal']]);
|
|
$body = ['version' => 1, 'enabled' => ['people.internal'], 'confirm' => true, 'reason' => 'Definir as opções adquiridas pela empresa.'];
|
|
$this->postJson('/administracao/parametrizacao/work_contexts', $body)->assertUnprocessable();
|
|
$body['enabled'] = ['procurement.internal'];
|
|
$this->postJson('/administracao/parametrizacao/work_contexts', $body)->assertRedirect();
|
|
$this->postJson('/administracao/parametrizacao/work_contexts', $body)->assertUnprocessable();
|
|
expect($this->f['tenant']->fresh()->work_context_policy)->toBe(['revision' => 2, 'enabled' => ['procurement.internal']])
|
|
->and(AuditLog::query()->where('action', 'organization.work_contexts')->count())->toBe(1);
|
|
});
|
|
|
|
test('SGF composition excludes third party hiring even if installation requests it', function () {
|
|
config(['work_context.composition' => 'sgf']);
|
|
expect(app(WorkContext::class)->purchased($this->actor))->not->toContain('people.third_party');
|
|
$this->post('/contexto', ['area' => 'people', 'option' => 'people.third_party'])->assertForbidden();
|
|
});
|
|
|
|
test('origin classification is internal and SQL cannot transfer a process to another context', function () {
|
|
expect($this->f['r']->fresh()->work_context_key)->toBe('procurement.internal');
|
|
expect(fn () => DB::table('requisitions')->where('id', $this->f['r']->id)->update(['work_context_key' => 'procurement.sales']))->toThrow(QueryException::class);
|
|
});
|
|
|
|
test('RH boundary refuses selection and draft contracts and performs no external request', function () {
|
|
Http::fake();
|
|
foreach (['selected', 'selection_review', 'contract_draft'] as $status) {
|
|
$record = new PeopleProcess(['tenant_id' => $this->actor->tenant_id, 'status' => $status, 'work_context_key' => 'people.internal', 'contract' => ['status' => 'draft']]);
|
|
expect(fn () => app(PeopleContractHandoff::class)->envelope($record))->toThrow(HttpException::class);
|
|
}
|
|
Http::assertNothingSent();
|
|
});
|
|
|
|
test('delivery check refuses the development fixture without deleting its records', function () {
|
|
$before = DB::table('requisitions')->count();
|
|
$this->artisan('sgc:delivery-check', ['tenant' => $this->actor->tenant_id])->assertFailed();
|
|
expect(DB::table('requisitions')->count())->toBe($before);
|
|
});
|
|
|
|
test('temporary maintenance may select its authorised context but cannot execute business commands', function () {
|
|
$tenant = $this->actor->tenant_id;
|
|
DB::table('organization_access')->insert(['tenant_id' => $tenant, 'active' => true, 'version' => 1, 'created_at' => now(), 'updated_at' => now()]);
|
|
$profile = DB::table('organization_profiles')->insertGetId(['tenant_id' => $tenant, 'name' => 'Consulta temporária', 'permissions' => json_encode(['procurement']), 'work_contexts' => json_encode(['procurement.internal']), 'version' => 1, 'created_at' => now(), 'updated_at' => now()]);
|
|
DB::table('organization_memberships')->insert(['tenant_id' => $tenant, 'user_id' => $this->actor->id, 'profile_id' => $profile, 'maintenance' => true, 'expires_at' => now()->addHour(), 'created_at' => now(), 'updated_at' => now()]);
|
|
$this->post('/contexto', ['area' => 'procurement'])->assertRedirect('/dashboard');
|
|
$this->get('/requisicoes')->assertOk();
|
|
$this->post('/requisicoes', [])->assertForbidden();
|
|
$this->post('/administracao/parametrizacao/work_contexts', [])->assertForbidden();
|
|
});
|