>> table => id => row */ public static array $store = []; /** @var list}> */ 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); } }