feat: implement tenant management system including CRUD operations and domain bootstrapping functionality
This commit is contained in:
@@ -19,10 +19,7 @@ class TenantController extends Controller
|
||||
|
||||
public function store(StoreTenantRequest $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$props = $validated['props'] ?? [];
|
||||
|
||||
$tenant = Tenant::createWithProps($validated, $props);
|
||||
$tenant = Tenant::query()->create($request->validated());
|
||||
|
||||
return TenantResource::make($tenant)->response()->setStatusCode(201);
|
||||
}
|
||||
@@ -34,10 +31,7 @@ class TenantController extends Controller
|
||||
|
||||
public function update(UpdateTenantRequest $request, Tenant $tenant): TenantResource
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$props = $validated['props'] ?? [];
|
||||
|
||||
$tenant->updateWithProps($validated, $props);
|
||||
$tenant->update($request->validated());
|
||||
|
||||
return TenantResource::make($tenant);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Controllers;
|
||||
|
||||
use App\Domains\Tenant\Models\TenantProp;
|
||||
use App\Domains\Tenant\Requests\StoreTenantPropRequest;
|
||||
use App\Domains\Tenant\Requests\UpdateTenantPropRequest;
|
||||
use App\Domains\Tenant\Resources\TenantPropResource;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class TenantPropController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
return TenantPropResource::collection(TenantProp::query()->latest()->get())->response();
|
||||
}
|
||||
|
||||
public function store(StoreTenantPropRequest $request): JsonResponse
|
||||
{
|
||||
$tenantProp = TenantProp::query()->create($request->validated());
|
||||
|
||||
return TenantPropResource::make($tenantProp)->response()->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(TenantProp $tenantProp): TenantPropResource
|
||||
{
|
||||
return TenantPropResource::make($tenantProp);
|
||||
}
|
||||
|
||||
public function update(UpdateTenantPropRequest $request, TenantProp $tenantProp): TenantPropResource
|
||||
{
|
||||
$tenantProp->update($request->validated());
|
||||
|
||||
return TenantPropResource::make($tenantProp);
|
||||
}
|
||||
|
||||
public function destroy(TenantProp $tenantProp): Response
|
||||
{
|
||||
$tenantProp->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,6 @@ use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use LogicException;
|
||||
|
||||
#[Fillable([
|
||||
'codigo',
|
||||
@@ -21,27 +19,6 @@ class Tenant extends Model
|
||||
use HasAttachments;
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attributes
|
||||
* @param array<string, mixed> $props
|
||||
* @return static
|
||||
*/
|
||||
public static function createWithProps(array $attributes, array $props = []): static
|
||||
{
|
||||
unset($attributes['props']);
|
||||
|
||||
/** @var static $tenant */
|
||||
$tenant = DB::transaction(function () use ($attributes, $props): self {
|
||||
/** @var static $createdTenant */
|
||||
$createdTenant = static::query()->create($attributes);
|
||||
$createdTenant->syncPropValues($props);
|
||||
|
||||
return $createdTenant;
|
||||
});
|
||||
|
||||
return $tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<Product, $this>
|
||||
*/
|
||||
@@ -49,77 +26,5 @@ class Tenant extends Model
|
||||
{
|
||||
return $this->hasMany(Product::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
/**
|
||||
* @return HasMany<TenantPropValue, $this>
|
||||
*/
|
||||
public function propValues(): HasMany
|
||||
{
|
||||
return $this->hasMany(TenantPropValue::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
|
||||
public function getPropValue(TenantProp|string $prop): ?TenantPropValue
|
||||
{
|
||||
$resolvedProp = $this->resolveProp($prop);
|
||||
|
||||
return $this->propValues()
|
||||
->where('tenant_prop_codigo', $resolvedProp->codigo)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function setPropValue(TenantProp|string $prop, mixed $value): TenantPropValue
|
||||
{
|
||||
$this->ensurePropValuesCanBeManaged();
|
||||
|
||||
$resolvedProp = $this->resolveProp($prop);
|
||||
|
||||
return $this->propValues()->updateOrCreate(
|
||||
['tenant_prop_codigo' => $resolvedProp->codigo],
|
||||
['value' => $value],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $props
|
||||
*/
|
||||
public function syncPropValues(array $props): void
|
||||
{
|
||||
foreach ($props as $codigo => $value) {
|
||||
$this->setPropValue((string) $codigo, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $attributes
|
||||
* @param array<string, mixed> $props
|
||||
* @return static
|
||||
*/
|
||||
public function updateWithProps(array $attributes, array $props = []): static
|
||||
{
|
||||
unset($attributes['props']);
|
||||
|
||||
DB::transaction(function () use ($attributes, $props): void {
|
||||
$this->update($attributes);
|
||||
$this->syncPropValues($props);
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function ensurePropValuesCanBeManaged(): void
|
||||
{
|
||||
if (! $this->exists) {
|
||||
throw new LogicException('Cannot manage prop values for an unsaved tenant.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function resolveProp(TenantProp|string $prop): TenantProp
|
||||
{
|
||||
if ($prop instanceof TenantProp) {
|
||||
return $prop;
|
||||
}
|
||||
|
||||
return TenantProp::query()
|
||||
->where('codigo', $prop)
|
||||
->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Models;
|
||||
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'codigo',
|
||||
'nombre',
|
||||
'descripcion',
|
||||
'is_required',
|
||||
'data_type',
|
||||
])]
|
||||
class TenantProp extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'tenant_props';
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_required' => 'boolean',
|
||||
'data_type' => FieldType::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<TenantPropValue, $this>
|
||||
*/
|
||||
public function values(): HasMany
|
||||
{
|
||||
return $this->hasMany(TenantPropValue::class, 'tenant_prop_codigo', 'codigo');
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable([
|
||||
'tenant_codigo',
|
||||
'tenant_prop_codigo',
|
||||
'value',
|
||||
])]
|
||||
class TenantPropValue extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'tenant_prop_values';
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Tenant, $this>
|
||||
*/
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<TenantProp, $this>
|
||||
*/
|
||||
public function prop(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TenantProp::class, 'tenant_prop_codigo', 'codigo');
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Requests;
|
||||
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreTenantPropRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'codigo' => ['required', 'string', 'max:255', Rule::unique('tenant_props', 'codigo')],
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
'descripcion' => ['nullable', 'string'],
|
||||
'is_required' => ['sometimes', 'boolean'],
|
||||
'data_type' => ['required', Rule::enum(FieldType::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Domains\Tenant\Requests;
|
||||
|
||||
use App\Domains\Tenant\Support\TenantPropRules;
|
||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
@@ -50,7 +49,6 @@ class StoreTenantRequest extends FormRequest
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'dominio'),
|
||||
],
|
||||
...TenantPropRules::sync(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Requests;
|
||||
|
||||
use App\Domains\Tenant\Models\TenantProp;
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTenantPropRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
/** @var TenantProp|null $tenantProp */
|
||||
$tenantProp = $this->route('tenantProp');
|
||||
|
||||
return [
|
||||
'codigo' => ['required', 'string', 'max:255', Rule::unique('tenant_props', 'codigo')->ignore($tenantProp?->id)],
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
'descripcion' => ['nullable', 'string'],
|
||||
'is_required' => ['sometimes', 'boolean'],
|
||||
'data_type' => ['required', Rule::enum(FieldType::class)],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace App\Domains\Tenant\Requests;
|
||||
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||
use App\Domains\Tenant\Support\TenantPropRules;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -59,7 +58,6 @@ class UpdateTenantRequest extends FormRequest
|
||||
'max:255',
|
||||
Rule::unique('tenants', 'dominio')->ignore($tenant?->id),
|
||||
],
|
||||
...TenantPropRules::sync(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin \App\Domains\Tenant\Models\TenantProp
|
||||
*/
|
||||
class TenantPropResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'codigo' => $this->codigo,
|
||||
'nombre' => $this->nombre,
|
||||
'descripcion' => $this->descripcion,
|
||||
'is_required' => $this->is_required,
|
||||
'data_type' => $this->data_type?->value ?? $this->data_type,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -10,29 +10,6 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
||||
*/
|
||||
class TenantResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function collapseProps(): array
|
||||
{
|
||||
return $this->resource->propValues()
|
||||
->with('prop')
|
||||
->get()
|
||||
->mapWithKeys(fn ($propValue): array => [
|
||||
$propValue->tenant_prop_codigo => $this->normalizePropValue($propValue->value, $propValue->prop?->data_type?->value),
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function normalizePropValue(mixed $value, ?string $dataType): mixed
|
||||
{
|
||||
return match ($dataType) {
|
||||
'boolean' => filter_var($value, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? $value,
|
||||
'number' => is_numeric($value) ? $value + 0 : $value,
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@@ -43,7 +20,6 @@ class TenantResource extends JsonResource
|
||||
'codigo' => $this->codigo,
|
||||
'nombre' => $this->nombre,
|
||||
'dominio' => $this->dominio,
|
||||
'props' => $this->collapseProps(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Tenant\Support;
|
||||
|
||||
use App\Domains\Tenant\Models\TenantProp;
|
||||
use Closure;
|
||||
|
||||
class TenantPropRules
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function sync(): array
|
||||
{
|
||||
return [
|
||||
'props' => [
|
||||
'sometimes',
|
||||
'array',
|
||||
static function (string $attribute, mixed $value, Closure $fail): void {
|
||||
static::validateExistingProps($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
'props.*' => ['nullable'],
|
||||
];
|
||||
}
|
||||
|
||||
protected static function validateExistingProps(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if (! is_array($value) || $value === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propCodes = array_map('strval', array_keys($value));
|
||||
$existingPropCodes = TenantProp::query()
|
||||
->whereIn('codigo', $propCodes)
|
||||
->pluck('codigo')
|
||||
->all();
|
||||
|
||||
$missingPropCodes = array_values(array_diff($propCodes, $existingPropCodes));
|
||||
|
||||
if ($missingPropCodes === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fail(sprintf(
|
||||
'The selected %s are invalid for Tenant: %s.',
|
||||
$attribute,
|
||||
implode(', ', $missingPropCodes),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,9 @@
|
||||
|
||||
use App\Domains\Tenant\Controllers\BootstrapTenantController;
|
||||
use App\Domains\Tenant\Controllers\TenantController;
|
||||
use App\Domains\Tenant\Controllers\TenantPropController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('tenants/bootstrap/{dominio}', BootstrapTenantController::class)
|
||||
->where('dominio', '.*');
|
||||
|
||||
Route::apiResource('tenants', TenantController::class);
|
||||
Route::apiResource('tenant-props', TenantPropController::class)
|
||||
->parameters(['tenant-props' => 'tenantProp']);
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('tenant_props', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('codigo')->unique();
|
||||
$table->string('nombre');
|
||||
$table->text('descripcion')->nullable();
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->string('data_type');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('tenant_props');
|
||||
}
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('tenant_prop_values', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('tenant_codigo');
|
||||
$table->string('tenant_prop_codigo');
|
||||
$table->text('value')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('tenant_codigo')
|
||||
->references('codigo')
|
||||
->on('tenants')
|
||||
->cascadeOnUpdate()
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreign('tenant_prop_codigo')
|
||||
->references('codigo')
|
||||
->on('tenant_props')
|
||||
->cascadeOnUpdate()
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->unique(['tenant_codigo', 'tenant_prop_codigo']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('tenant_prop_values');
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Tests\Feature\Tenant;
|
||||
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\TenantProp;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -11,7 +10,7 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_it_bootstraps_a_tenant_by_domain_and_includes_props(): void
|
||||
public function test_it_bootstraps_a_tenant_by_domain(): void
|
||||
{
|
||||
$tenant = Tenant::create([
|
||||
'codigo' => 'acme',
|
||||
@@ -19,23 +18,14 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'dominio' => 'acme.com',
|
||||
]);
|
||||
|
||||
TenantProp::create([
|
||||
'codigo' => 'primary_color',
|
||||
'nombre' => 'Primary Color',
|
||||
'descripcion' => 'Brand color',
|
||||
'is_required' => false,
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
|
||||
$tenant->setPropValue('primary_color', 'blue');
|
||||
|
||||
$response = $this->getJson('/api/tenants/bootstrap/acme.com');
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertJsonPath('data.codigo', 'acme')
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.props.primary_color', 'blue');
|
||||
->assertJsonPath('data.dominio', 'acme.com');
|
||||
|
||||
$this->assertArrayNotHasKey('props', $response->json('data'));
|
||||
}
|
||||
|
||||
public function test_it_bootstraps_a_tenant_from_a_full_url(): void
|
||||
@@ -65,33 +55,15 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
|
||||
public function test_it_rejects_duplicate_domains_after_normalization_when_storing(): void
|
||||
{
|
||||
TenantProp::create([
|
||||
'codigo' => 'primary_color',
|
||||
'nombre' => 'Primary Color',
|
||||
'descripcion' => 'Brand color',
|
||||
'is_required' => false,
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
|
||||
$firstResponse = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'dominio' => 'https://ACME.com/path',
|
||||
'props' => [
|
||||
'primary_color' => 'blue',
|
||||
],
|
||||
]);
|
||||
|
||||
$firstResponse
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.props.primary_color', 'blue');
|
||||
|
||||
$this->assertDatabaseHas('tenant_prop_values', [
|
||||
'tenant_codigo' => 'acme',
|
||||
'tenant_prop_codigo' => 'primary_color',
|
||||
'value' => 'blue',
|
||||
]);
|
||||
->assertJsonPath('data.dominio', 'acme.com');
|
||||
|
||||
$secondResponse = $this->postJson('/api/tenants', [
|
||||
'codigo' => 'globex',
|
||||
@@ -106,14 +78,6 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
|
||||
public function test_it_allows_keeping_the_same_domain_on_update_but_rejects_collisions(): void
|
||||
{
|
||||
TenantProp::create([
|
||||
'codigo' => 'primary_color',
|
||||
'nombre' => 'Primary Color',
|
||||
'descripcion' => 'Brand color',
|
||||
'is_required' => false,
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
|
||||
$tenant = Tenant::create([
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
@@ -130,16 +94,12 @@ class BootstrapTenantControllerTest extends TestCase
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme Updated',
|
||||
'dominio' => 'https://ACME.com:443/admin',
|
||||
'props' => [
|
||||
'primary_color' => 'green',
|
||||
],
|
||||
]);
|
||||
|
||||
$successfulResponse
|
||||
->assertOk()
|
||||
->assertJsonPath('data.nombre', 'Acme Updated')
|
||||
->assertJsonPath('data.dominio', 'acme.com')
|
||||
->assertJsonPath('data.props.primary_color', 'green');
|
||||
->assertJsonPath('data.dominio', 'acme.com');
|
||||
|
||||
$failingResponse = $this->putJson("/api/tenants/{$otherTenant->id}", [
|
||||
'codigo' => 'globex',
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Tenant;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TenantPropControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_it_creates_a_tenant_prop(): void
|
||||
{
|
||||
$response = $this->postJson('/api/tenant-props', [
|
||||
'codigo' => 'primary_color',
|
||||
'nombre' => 'Primary Color',
|
||||
'descripcion' => 'Brand color',
|
||||
'is_required' => false,
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertCreated()
|
||||
->assertJsonPath('data.codigo', 'primary_color')
|
||||
->assertJsonPath('data.data_type', 'string');
|
||||
|
||||
$this->assertDatabaseHas('tenant_props', [
|
||||
'codigo' => 'primary_color',
|
||||
'data_type' => 'string',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_it_rejects_unknown_data_type(): void
|
||||
{
|
||||
$response = $this->postJson('/api/tenant-props', [
|
||||
'codigo' => 'primary_color',
|
||||
'nombre' => 'Primary Color',
|
||||
'data_type' => 'unsupported',
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors(['data_type']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user