234 lines
7.6 KiB
PHP
234 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Support;
|
|
|
|
use Illuminate\Http\Client\Request;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
class InsForgeFake
|
|
{
|
|
/** @var array<string, array<string, array<string, mixed>>> table => id => row */
|
|
public static array $store = [];
|
|
|
|
/** @var list<array{function: string, args: array<string, mixed>}> */
|
|
public static array $rpcCalls = [];
|
|
|
|
public static string $accessToken = '';
|
|
|
|
public static function reset(): void
|
|
{
|
|
self::$store = [];
|
|
self::$rpcCalls = [];
|
|
}
|
|
|
|
public static function seed(string $table, array $row): void
|
|
{
|
|
self::$store[$table][$row['id']] = $row;
|
|
}
|
|
|
|
public static function fakeLoginSuccess(array $profile): void
|
|
{
|
|
self::reset();
|
|
|
|
self::$accessToken = 'eyJhbGciOiJIUzI1NiJ9.'.base64_encode(json_encode([
|
|
'sub' => $profile['id'],
|
|
'exp' => now()->addHour()->getTimestamp(),
|
|
])).'.sig';
|
|
|
|
self::seed('users', $profile);
|
|
|
|
Http::fake([
|
|
'*/api/auth/sessions' => Http::response([
|
|
'accessToken' => self::$accessToken,
|
|
'refreshToken' => 'fake-refresh-token',
|
|
'user' => [
|
|
'id' => $profile['id'],
|
|
'email' => $profile['email'],
|
|
'name' => $profile['name'],
|
|
],
|
|
], 200, [
|
|
'Set-Cookie' => 'insforge_refresh_token=fake-refresh-cookie; Path=/; HttpOnly',
|
|
]),
|
|
'*/api/auth/refresh' => Http::response(['accessToken' => self::$accessToken], 200),
|
|
'*/api/database/records/*' => fn (Request $request) => self::handleRecordsRequest($request),
|
|
]);
|
|
}
|
|
|
|
public static function fakeLoginFailure(): void
|
|
{
|
|
self::reset();
|
|
|
|
Http::fake([
|
|
'*/api/auth/sessions' => Http::response([
|
|
'error' => 'AUTH_UNAUTHORIZED',
|
|
'message' => 'Invalid credentials',
|
|
'statusCode' => 401,
|
|
], 401),
|
|
]);
|
|
}
|
|
|
|
public static function fakeRest(): void
|
|
{
|
|
Http::fake([
|
|
'*/api/auth/refresh' => Http::response(['accessToken' => self::$accessToken], 200),
|
|
'*/api/database/rpc/*' => function (Request $request) {
|
|
$function = str_replace('/api/database/rpc/', '', (string) parse_url($request->url(), PHP_URL_PATH));
|
|
$args = $request->data();
|
|
|
|
self::$rpcCalls[] = ['function' => $function, 'args' => $args];
|
|
|
|
if ($function === 'complete_order') {
|
|
return self::simulateCompleteOrder($args);
|
|
}
|
|
|
|
return Http::response(['ok' => true], 200);
|
|
},
|
|
'*/api/database/records/*' => fn (Request $request) => self::handleRecordsRequest($request),
|
|
]);
|
|
}
|
|
|
|
public static function handleRecordsRequest(Request $request)
|
|
{
|
|
$path = parse_url($request->url(), PHP_URL_PATH) ?? '';
|
|
$table = str_replace('/api/database/records/', '', $path);
|
|
parse_str((string) parse_url($request->url(), PHP_URL_QUERY) ?? '', $query);
|
|
$method = strtoupper($request->method());
|
|
|
|
$rows = array_values(self::$store[$table] ?? []);
|
|
|
|
if ($method === 'GET') {
|
|
foreach ($query as $key => $value) {
|
|
if (in_array($key, ['select', 'limit', 'offset', 'order', 'or'], true)) {
|
|
continue;
|
|
}
|
|
|
|
if (str_starts_with((string) $value, 'eq.')) {
|
|
$column = $key;
|
|
$expected = substr((string) $value, 3);
|
|
|
|
if ($expected === 'null') {
|
|
$rows = array_values(array_filter($rows, fn (array $row): bool => ($row[$column] ?? null) === null));
|
|
} else {
|
|
$rows = array_values(array_filter($rows, fn (array $row): bool => (string) ($row[$column] ?? '') === $expected));
|
|
}
|
|
}
|
|
}
|
|
|
|
$total = count($rows);
|
|
|
|
$rows = array_slice(
|
|
$rows,
|
|
(int) ($query['offset'] ?? 0),
|
|
(int) ($query['limit'] ?? 25),
|
|
);
|
|
|
|
return Http::response(
|
|
array_map(
|
|
fn (array $row): array => isset($query['select']) && $query['select'] !== '*'
|
|
? array_intersect_key($row, array_flip(explode(',', (string) $query['select']))) ?: $row
|
|
: $row,
|
|
$rows,
|
|
),
|
|
200,
|
|
['Content-Range' => sprintf('0-%d/%d', max(count($rows) - 1, 0), $total)],
|
|
);
|
|
}
|
|
|
|
if ($method === 'POST') {
|
|
$payload = $request->data()[0] ?? [];
|
|
|
|
if (! is_array($payload)) {
|
|
return Http::response([], 201);
|
|
}
|
|
|
|
$payload = array_merge($payload, ['id' => $payload['id'] ?? fake()->uuid()]);
|
|
|
|
self::$store[$table][$payload['id']] = $payload;
|
|
|
|
return Http::response([], 201);
|
|
}
|
|
|
|
$targetId = str_replace('eq.', '', $query['id'] ?? '');
|
|
|
|
if ($method === 'PATCH') {
|
|
$payload = $request->data();
|
|
|
|
foreach (self::$store[$table] as &$row) {
|
|
if (($row['id'] ?? null) === $targetId) {
|
|
$row = array_merge($row, $payload);
|
|
}
|
|
}
|
|
unset($row);
|
|
|
|
return Http::response([], 204);
|
|
}
|
|
|
|
if ($method === 'DELETE') {
|
|
unset(self::$store[$table][$targetId]);
|
|
|
|
return Http::response([], 204);
|
|
}
|
|
|
|
return Http::response([], 200);
|
|
}
|
|
|
|
/**
|
|
* Tiruan semantik server: idempotent via gerbang unik (reference_type, reference_id),
|
|
* decrement stok sekali, set status completed + history.
|
|
*/
|
|
private static function simulateCompleteOrder(array $args)
|
|
{
|
|
$orderId = $args['p_order_id'] ?? null;
|
|
|
|
if ($orderId === null || ! isset(self::$store['orders'][$orderId])) {
|
|
return Http::response(['error' => 'ORDER_NOT_FOUND'], 500);
|
|
}
|
|
|
|
$order = self::$store['orders'][$orderId];
|
|
|
|
if (($order['order_status'] ?? '') === 'completed') {
|
|
return Http::response(['ok' => true], 200);
|
|
}
|
|
|
|
if (($order['order_status'] ?? '') !== 'delivering') {
|
|
return Http::response(['error' => 'INVALID_TRANSITION'], 500);
|
|
}
|
|
|
|
foreach (self::$store['order_items'] ?? [] as $item) {
|
|
if (($item['order_id'] ?? null) !== $orderId) {
|
|
continue;
|
|
}
|
|
|
|
$alreadyOut = collect(self::$store['stock_movements'] ?? [])->contains(
|
|
fn (array $m): bool => $m['type'] === 'order_out'
|
|
&& ($m['reference_id'] ?? null) === $orderId
|
|
&& ($m['product_id'] ?? null) === $item['product_id'],
|
|
);
|
|
|
|
if ($alreadyOut) {
|
|
continue;
|
|
}
|
|
|
|
self::seed('stock_movements', [
|
|
'id' => fake()->uuid(),
|
|
'product_id' => $item['product_id'],
|
|
'type' => 'order_out',
|
|
'quantity' => -1 * (int) $item['quantity'],
|
|
'reference_type' => 'order',
|
|
'reference_id' => $orderId,
|
|
]);
|
|
|
|
foreach (self::$store['stocks'] as &$stock) {
|
|
if (($stock['product_id'] ?? null) === $item['product_id']) {
|
|
$stock['quantity'] -= (int) $item['quantity'];
|
|
}
|
|
}
|
|
unset($stock);
|
|
}
|
|
|
|
self::$store['orders'][$orderId]['order_status'] = 'completed';
|
|
|
|
return Http::response(['ok' => true], 200);
|
|
}
|
|
}
|