Files

266 lines
9.0 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 = [];
/** @var array<string, string> email => auth uuid */
public static array $authUsersByEmail = [];
/** @var list<string> */
public static array $rawSqlQueries = [];
public static string $accessToken = '';
public static function reset(): void
{
self::$store = [];
self::$rpcCalls = [];
self::$authUsersByEmail = [];
self::$rawSqlQueries = [];
}
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/auth/users' => function (Request $request) {
if (strtoupper($request->method()) === 'POST') {
$payload = $request->data();
self::$authUsersByEmail[$payload['email'] ?? ''] = 'auth-'.md5((string) ($payload['email'] ?? ''));
return Http::response(['accessToken' => null, 'requireEmailVerification' => true], 200);
}
return Http::response([], 200);
},
'*/api/database/advance/rawsql*' => function (Request $request) {
$query = (string) ($request->data()['query'] ?? '');
self::$rawSqlQueries[] = $query;
if (str_contains($query, 'FROM auth.users') && str_contains(strtoupper($query), 'SELECT')) {
preg_match("/email = '([^']+)'/", $query, $m);
$id = self::$authUsersByEmail[$m[1] ?? ''] ?? null;
return Http::response(['rows' => $id ? [['id' => $id]] : []], 200);
}
return Http::response(['rows' => []], 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);
}
}