feat: implement tenant management system including database schema, CRUD services, and bootstrap functionality
This commit is contained in:
@@ -6,12 +6,17 @@ use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Requests\StoreTenantRequest;
|
||||
use App\Domains\Tenant\Requests\UpdateTenantRequest;
|
||||
use App\Domains\Tenant\Resources\TenantResource;
|
||||
use App\Domains\Tenant\Services\TenantService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class TenantController extends Controller
|
||||
{
|
||||
public function __construct(protected TenantService $tenantService)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
return TenantResource::collection(Tenant::query()->latest()->get())->response();
|
||||
@@ -19,7 +24,7 @@ class TenantController extends Controller
|
||||
|
||||
public function store(StoreTenantRequest $request): JsonResponse
|
||||
{
|
||||
$tenant = Tenant::query()->create($request->validated());
|
||||
$tenant = $this->tenantService->create($request->validated());
|
||||
|
||||
return TenantResource::make($tenant)->response()->setStatusCode(201);
|
||||
}
|
||||
@@ -31,7 +36,7 @@ class TenantController extends Controller
|
||||
|
||||
public function update(UpdateTenantRequest $request, Tenant $tenant): TenantResource
|
||||
{
|
||||
$tenant->update($request->validated());
|
||||
$tenant = $this->tenantService->update($tenant, $request->validated());
|
||||
|
||||
return TenantResource::make($tenant);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
'codigo',
|
||||
'nombre',
|
||||
'dominio',
|
||||
'primary_color',
|
||||
'secondary_color',
|
||||
'danger_color',
|
||||
'header_footer_bg_color',
|
||||
'header_logo',
|
||||
'footer_logo',
|
||||
])]
|
||||
class Tenant extends Model
|
||||
{
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Domains\Tenant\Requests;
|
||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreTenantRequest extends FormRequest
|
||||
@@ -34,6 +36,52 @@ class StoreTenantRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$logoRule = [
|
||||
'nullable',
|
||||
static function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value instanceof UploadedFile) {
|
||||
$mime = $value->getMimeType();
|
||||
if (! str_starts_with($mime, 'image/')) {
|
||||
$fail("The {$attribute} must be a valid image or SVG.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
if (Str::isUuid($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$payload = trim($value);
|
||||
$declaredMimeType = null;
|
||||
if (preg_match('/^data:(?<mime>[-\w.+\/]+);base64,(?<data>.+)$/s', $payload, $matches) === 1) {
|
||||
$declaredMimeType = strtolower($matches['mime']);
|
||||
$payload = $matches['data'];
|
||||
}
|
||||
$decoded = base64_decode(preg_replace('/\s+/', '', $payload), true);
|
||||
if ($decoded === false || $decoded === '') {
|
||||
$fail("The {$attribute} must be a valid base64 image or SVG.");
|
||||
return;
|
||||
}
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->buffer($decoded);
|
||||
if (! $mime && $declaredMimeType) {
|
||||
$mime = $declaredMimeType;
|
||||
}
|
||||
if (! $mime || ! str_starts_with($mime, 'image/')) {
|
||||
$fail("The {$attribute} must be a valid image or SVG.");
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
$fail("The {$attribute} must be a valid image or SVG.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$fail("The {$attribute} must be a valid file or base64 string.");
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
'codigo' => ['required', 'string', 'max:255', Rule::unique('tenants', 'codigo')],
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
@@ -49,6 +97,12 @@ class StoreTenantRequest extends FormRequest
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'dominio'),
|
||||
],
|
||||
'primary_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'secondary_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'danger_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'header_footer_bg_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'header_logo' => $logoRule,
|
||||
'footer_logo' => $logoRule,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTenantRequest extends FormRequest
|
||||
@@ -38,6 +40,52 @@ class UpdateTenantRequest extends FormRequest
|
||||
/** @var Tenant|null $tenant */
|
||||
$tenant = $this->route('tenant');
|
||||
|
||||
$logoRule = [
|
||||
'nullable',
|
||||
static function (string $attribute, mixed $value, Closure $fail): void {
|
||||
if ($value instanceof UploadedFile) {
|
||||
$mime = $value->getMimeType();
|
||||
if (! str_starts_with($mime, 'image/')) {
|
||||
$fail("The {$attribute} must be a valid image or SVG.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
if (Str::isUuid($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$payload = trim($value);
|
||||
$declaredMimeType = null;
|
||||
if (preg_match('/^data:(?<mime>[-\w.+\/]+);base64,(?<data>.+)$/s', $payload, $matches) === 1) {
|
||||
$declaredMimeType = strtolower($matches['mime']);
|
||||
$payload = $matches['data'];
|
||||
}
|
||||
$decoded = base64_decode(preg_replace('/\s+/', '', $payload), true);
|
||||
if ($decoded === false || $decoded === '') {
|
||||
$fail("The {$attribute} must be a valid base64 image or SVG.");
|
||||
return;
|
||||
}
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->buffer($decoded);
|
||||
if (! $mime && $declaredMimeType) {
|
||||
$mime = $declaredMimeType;
|
||||
}
|
||||
if (! $mime || ! str_starts_with($mime, 'image/')) {
|
||||
$fail("The {$attribute} must be a valid image or SVG.");
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
$fail("The {$attribute} must be a valid image or SVG.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$fail("The {$attribute} must be a valid file or base64 string.");
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
'codigo' => [
|
||||
'required',
|
||||
@@ -58,6 +106,12 @@ class UpdateTenantRequest extends FormRequest
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'dominio')->ignore($tenant?->id),
|
||||
],
|
||||
'primary_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'secondary_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'danger_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'header_footer_bg_color' => ['nullable', 'string', 'regex:/^#([a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/'],
|
||||
'header_logo' => $logoRule,
|
||||
'footer_logo' => $logoRule,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ class TenantResource extends JsonResource
|
||||
'codigo' => $this->codigo,
|
||||
'nombre' => $this->nombre,
|
||||
'dominio' => $this->dominio,
|
||||
'primary_color' => $this->primary_color,
|
||||
'secondary_color' => $this->secondary_color,
|
||||
'danger_color' => $this->danger_color,
|
||||
'header_footer_bg_color' => $this->header_footer_bg_color,
|
||||
'header_logo' => $this->header_logo,
|
||||
'footer_logo' => $this->footer_logo,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
105
app/Domains/Tenant/Services/TenantService.php
Normal file
105
app/Domains/Tenant/Services/TenantService.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Services;
|
||||
|
||||
use App\Domains\Attachable\Services\AttachmentService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TenantService
|
||||
{
|
||||
public function __construct(protected AttachmentService $attachmentService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new tenant and store its logos.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return Tenant
|
||||
*/
|
||||
public function create(array $data): Tenant
|
||||
{
|
||||
return DB::transaction(function () use ($data): Tenant {
|
||||
$headerLogo = $data['header_logo'] ?? null;
|
||||
$footerLogo = $data['footer_logo'] ?? null;
|
||||
|
||||
unset($data['header_logo'], $data['footer_logo']);
|
||||
|
||||
/** @var Tenant $tenant */
|
||||
$tenant = Tenant::query()->create($data);
|
||||
|
||||
if ($headerLogo) {
|
||||
$attachment = $this->attachmentService->store($headerLogo, 'tenants');
|
||||
$tenant->header_logo = $attachment->key;
|
||||
$tenant->attachments()->attach($attachment->id);
|
||||
}
|
||||
|
||||
if ($footerLogo) {
|
||||
$attachment = $this->attachmentService->store($footerLogo, 'tenants');
|
||||
$tenant->footer_logo = $attachment->key;
|
||||
$tenant->attachments()->attach($attachment->id);
|
||||
}
|
||||
|
||||
if ($headerLogo || $footerLogo) {
|
||||
$tenant->save();
|
||||
}
|
||||
|
||||
return $tenant;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing tenant and store new logos if uploaded.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @param array<string, mixed> $data
|
||||
* @return Tenant
|
||||
*/
|
||||
public function update(Tenant $tenant, array $data): Tenant
|
||||
{
|
||||
return DB::transaction(function () use ($tenant, $data): Tenant {
|
||||
$hasHeaderLogoKey = array_key_exists('header_logo', $data);
|
||||
$hasFooterLogoKey = array_key_exists('footer_logo', $data);
|
||||
$headerLogo = $data['header_logo'] ?? null;
|
||||
$footerLogo = $data['footer_logo'] ?? null;
|
||||
|
||||
unset($data['header_logo'], $data['footer_logo']);
|
||||
|
||||
$tenant->fill($data);
|
||||
|
||||
if ($hasHeaderLogoKey) {
|
||||
if ($headerLogo) {
|
||||
if (! Str::isUuid($headerLogo)) {
|
||||
$attachment = $this->attachmentService->store($headerLogo, 'tenants');
|
||||
$tenant->header_logo = $attachment->key;
|
||||
$tenant->attachments()->attach($attachment->id);
|
||||
} else {
|
||||
$tenant->header_logo = $headerLogo;
|
||||
}
|
||||
} else {
|
||||
$tenant->header_logo = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasFooterLogoKey) {
|
||||
if ($footerLogo) {
|
||||
if (! Str::isUuid($footerLogo)) {
|
||||
$attachment = $this->attachmentService->store($footerLogo, 'tenants');
|
||||
$tenant->footer_logo = $attachment->key;
|
||||
$tenant->attachments()->attach($attachment->id);
|
||||
} else {
|
||||
$tenant->footer_logo = $footerLogo;
|
||||
}
|
||||
} else {
|
||||
$tenant->footer_logo = null;
|
||||
}
|
||||
}
|
||||
|
||||
$tenant->save();
|
||||
|
||||
return $tenant;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@ return new class extends Migration
|
||||
$table->string('codigo')->unique();
|
||||
$table->string('nombre');
|
||||
$table->string('dominio')->nullable();
|
||||
$table->string('primary_color')->nullable();
|
||||
$table->string('secondary_color')->nullable();
|
||||
$table->string('danger_color')->nullable();
|
||||
$table->string('header_footer_bg_color')->nullable();
|
||||
$table->string('header_logo')->nullable();
|
||||
$table->string('footer_logo')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Tests\Feature\Tenant;
|
||||
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BootstrapTenantControllerTest extends TestCase
|
||||
@@ -16,6 +19,12 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'dominio' => 'acme.com',
|
||||
'primary_color' => '#ff0000',
|
||||
'secondary_color' => '#00ff00',
|
||||
'danger_color' => '#0000ff',
|
||||
'header_footer_bg_color' => '#ffffff',
|
||||
'header_logo' => 'logo_header.png',
|
||||
'footer_logo' => 'logo_footer.png',
|
||||
]);
|
||||
|
||||
$response = $this->getJson('/api/tenants/bootstrap/acme.com');
|
||||
@@ -23,7 +32,13 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
$response
|
||||
->assertOk()
|
||||
->assertJsonPath('data.codigo', 'acme')
|
||||
->assertJsonPath('data.dominio', 'acme.com');
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.primary_color', '#ff0000')
|
||||
->assertJsonPath('data.secondary_color', '#00ff00')
|
||||
->assertJsonPath('data.danger_color', '#0000ff')
|
||||
->assertJsonPath('data.header_footer_bg_color', '#ffffff')
|
||||
->assertJsonPath('data.header_logo', 'logo_header.png')
|
||||
->assertJsonPath('data.footer_logo', 'logo_footer.png');
|
||||
|
||||
$this->assertArrayNotHasKey('props', $response->json('data'));
|
||||
}
|
||||
@@ -55,15 +70,43 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
|
||||
public function test_it_rejects_duplicate_domains_after_normalization_when_storing(): void
|
||||
{
|
||||
$base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
||||
|
||||
$firstResponse = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'dominio' => 'https://ACME.com/path',
|
||||
'primary_color' => '#111111',
|
||||
'secondary_color' => '#222222',
|
||||
'danger_color' => '#333333',
|
||||
'header_footer_bg_color' => '#444444',
|
||||
'header_logo' => $base64Image,
|
||||
'footer_logo' => $base64Image,
|
||||
]);
|
||||
|
||||
$firstResponse
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.dominio', 'acme.com');
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.primary_color', '#111111')
|
||||
->assertJsonPath('data.secondary_color', '#222222')
|
||||
->assertJsonPath('data.danger_color', '#333333')
|
||||
->assertJsonPath('data.header_footer_bg_color', '#444444');
|
||||
|
||||
$headerUuid = $firstResponse->json('data.header_logo');
|
||||
$footerUuid = $firstResponse->json('data.footer_logo');
|
||||
|
||||
$this->assertTrue(Str::isUuid($headerUuid));
|
||||
$this->assertTrue(Str::isUuid($footerUuid));
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'codigo' => 'acme',
|
||||
'primary_color' => '#111111',
|
||||
'secondary_color' => '#222222',
|
||||
'danger_color' => '#333333',
|
||||
'header_footer_bg_color' => '#444444',
|
||||
'header_logo' => $headerUuid,
|
||||
'footer_logo' => $footerUuid,
|
||||
]);
|
||||
|
||||
$secondResponse = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'globex',
|
||||
@@ -90,16 +133,41 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'dominio' => 'globex.com',
|
||||
]);
|
||||
|
||||
$hdrUuid = (string) Str::uuid();
|
||||
$ftrUuid = (string) Str::uuid();
|
||||
|
||||
$successfulResponse = $this->putJson("/api/tenants/{$tenant->id}", [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme Updated',
|
||||
'dominio' => 'https://ACME.com:443/admin',
|
||||
'primary_color' => '#555555',
|
||||
'secondary_color' => '#666666',
|
||||
'danger_color' => '#777777',
|
||||
'header_footer_bg_color' => '#888888',
|
||||
'header_logo' => $hdrUuid,
|
||||
'footer_logo' => $ftrUuid,
|
||||
]);
|
||||
|
||||
$successfulResponse
|
||||
->assertOk()
|
||||
->assertJsonPath('data.nombre', 'Acme Updated')
|
||||
->assertJsonPath('data.dominio', 'acme.com');
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.primary_color', '#555555')
|
||||
->assertJsonPath('data.secondary_color', '#666666')
|
||||
->assertJsonPath('data.danger_color', '#777777')
|
||||
->assertJsonPath('data.header_footer_bg_color', '#888888')
|
||||
->assertJsonPath('data.header_logo', $hdrUuid)
|
||||
->assertJsonPath('data.footer_logo', $ftrUuid);
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'id' => $tenant->id,
|
||||
'primary_color' => '#555555',
|
||||
'secondary_color' => '#666666',
|
||||
'danger_color' => '#777777',
|
||||
'header_footer_bg_color' => '#888888',
|
||||
'header_logo' => $hdrUuid,
|
||||
'footer_logo' => $ftrUuid,
|
||||
]);
|
||||
|
||||
$failingResponse = $this->putJson("/api/tenants/{$otherTenant->id}", [
|
||||
'codigo' => 'globex',
|
||||
@@ -111,4 +179,89 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['dominio']);
|
||||
}
|
||||
|
||||
public function test_it_validates_aesthetic_colors(): void
|
||||
{
|
||||
$response = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'primary_color' => 'invalid-color',
|
||||
]);
|
||||
|
||||
$response->assertJsonValidationErrors(['primary_color']);
|
||||
|
||||
$response2 = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'primary_color' => '#12345',
|
||||
]);
|
||||
|
||||
$response2->assertJsonValidationErrors(['primary_color']);
|
||||
}
|
||||
|
||||
public function test_it_validates_logo_must_be_image_or_svg(): void
|
||||
{
|
||||
Storage::fake('s3');
|
||||
|
||||
$response = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'header_logo' => UploadedFile::fake()->create('document.pdf', 10, 'application/pdf'),
|
||||
]);
|
||||
|
||||
$response->assertJsonValidationErrors(['header_logo']);
|
||||
|
||||
$response2 = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'header_logo' => 'data:application/pdf;base64,JVBERi0xLjQKJdcfqksKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAvUGFnZXMgMiAwIFI...',
|
||||
]);
|
||||
|
||||
$response2->assertJsonValidationErrors(['header_logo']);
|
||||
}
|
||||
|
||||
public function test_it_stores_uploaded_file_logos_in_tenants_directory(): void
|
||||
{
|
||||
Storage::fake('s3');
|
||||
|
||||
$header = UploadedFile::fake()->image('header.png');
|
||||
$footer = UploadedFile::fake()->image('footer.svg', 100, 100);
|
||||
|
||||
$response = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'header_logo' => $header,
|
||||
'footer_logo' => $footer,
|
||||
]);
|
||||
|
||||
$response->assertCreated();
|
||||
|
||||
$headerUuid = $response->json('data.header_logo');
|
||||
$footerUuid = $response->json('data.footer_logo');
|
||||
|
||||
$this->assertTrue(Str::isUuid($headerUuid));
|
||||
$this->assertTrue(Str::isUuid($footerUuid));
|
||||
|
||||
$this->assertDatabaseHas('attachments', ['key' => $headerUuid]);
|
||||
$this->assertDatabaseHas('attachments', ['key' => $footerUuid]);
|
||||
}
|
||||
|
||||
public function test_it_stores_base64_logos_in_tenants_directory(): void
|
||||
{
|
||||
Storage::fake('s3');
|
||||
|
||||
$base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
||||
|
||||
$response = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'header_logo' => $base64Image,
|
||||
]);
|
||||
|
||||
$response->assertCreated();
|
||||
|
||||
$headerUuid = $response->json('data.header_logo');
|
||||
$this->assertTrue(Str::isUuid($headerUuid));
|
||||
$this->assertDatabaseHas('attachments', ['key' => $headerUuid]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user