import { EnvironmentId } from "@t3tools/contracts";import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; const mocks = vi.hoisted(() => { const values = new Map<string, string>(); return { clear: () => values.clear(), getItemAsync: vi.fn((key: string) => Promise.resolve(values.get(key) ?? null)), setItemAsync: vi.fn((key: string, value: string) => { values.set(key, value); return Promise.resolve(); }), };}); vi.mock("expo-secure-store", () => ({ getItemAsync: mocks.getItemAsync, setItemAsync: mocks.setItemAsync,})); vi.mock("react-native", () => ({ Platform: { OS: "ios", },})); vi.mock("./runtime", () => ({ runtime: { runPromise: vi.fn(), },})); import { loadSavedConnections, saveConnection } from "./storage";import { toStableSavedRemoteConnection } from "./connection"; const managedConnection = { environmentId: EnvironmentId.make("environment-1"), environmentLabel: "Desktop", pairingUrl: "https://desktop.example/", displayUrl: "https://desktop.example/", httpBaseUrl: "https://desktop.example/", wsBaseUrl: "wss://desktop.example/", bearerToken: null, authenticationMethod: "dpop", dpopAccessToken: "short-lived-token", relayManaged: true,} as const; describe("mobile connection storage", () => { beforeEach(() => { mocks.clear(); vi.clearAllMocks(); }); it("persists relay-managed connections without their ephemeral access token", async () => { await saveConnection(managedConnection); const savedValue = mocks.setItemAsync.mock.calls[0]?.[1]; expect(savedValue).toBeDefined(); expect(JSON.parse(savedValue ?? "")).toEqual({ connections: [toStableSavedRemoteConnection(managedConnection)], }); }); it("loads relay-managed connection metadata without a cached access token", async () => { await saveConnection(managedConnection); await expect(loadSavedConnections()).resolves.toEqual([ toStableSavedRemoteConnection(managedConnection), ]); }); it("preserves secure-storage read failures with operation and key context", async () => { const cause = new Error("keychain unavailable"); mocks.getItemAsync.mockRejectedValueOnce(cause); await expect(loadSavedConnections()).rejects.toMatchObject({ _tag: "MobileSecureStorageError", operation: "read", key: "t3code.connections", cause, message: "Mobile secure storage operation read failed for key t3code.connections.", }); }); it("logs structured decode failures before using the empty fallback", async () => { await mocks.setItemAsync("t3code.connections", "{"); const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined); await expect(loadSavedConnections()).resolves.toEqual([]); expect(warn).toHaveBeenCalledWith( "[mobile-storage] ignored invalid JSON", expect.objectContaining({ _tag: "MobileStorageDecodeError", key: "t3code.connections", cause: expect.any(SyntaxError), message: "Failed to decode mobile storage value for key t3code.connections.", }), ); warn.mockRestore(); });});
import { EnvironmentId } from "@t3tools/contracts";
import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
const mocks = vi.hoisted(() => {
const values = new Map<string, string>();
return {
clear: () => values.clear(),
getItemAsync: vi.fn((key: string) => Promise.resolve(values.get(key) ?? null)),
setItemAsync: vi.fn((key: string, value: string) => {
values.set(key, value);
return Promise.resolve();
}),
};
});
vi.mock("expo-secure-store", () => ({
getItemAsync: mocks.getItemAsync,
setItemAsync: mocks.setItemAsync,
}));
vi.mock("react-native", () => ({
Platform: {
OS: "ios",
},
}));
vi.mock("./runtime", () => ({
runtime: {
runPromise: vi.fn(),
},
}));
import { loadSavedConnections, saveConnection } from "./storage";
import { toStableSavedRemoteConnection } from "./connection";
const managedConnection = {
environmentId: EnvironmentId.make("environment-1"),
environmentLabel: "Desktop",
pairingUrl: "https://desktop.example/",
displayUrl: "https://desktop.example/",
httpBaseUrl: "https://desktop.example/",
wsBaseUrl: "wss://desktop.example/",
bearerToken: null,
authenticationMethod: "dpop",
dpopAccessToken: "short-lived-token",
relayManaged: true,
} as const;
describe("mobile connection storage", () => {
beforeEach(() => {
mocks.clear();
vi.clearAllMocks();
});
it("persists relay-managed connections without their ephemeral access token", async () => {
await saveConnection(managedConnection);
const savedValue = mocks.setItemAsync.mock.calls[0]?.[1];
expect(savedValue).toBeDefined();
expect(JSON.parse(savedValue ?? "")).toEqual({
connections: [toStableSavedRemoteConnection(managedConnection)],
});
});
it("loads relay-managed connection metadata without a cached access token", async () => {
await saveConnection(managedConnection);
await expect(loadSavedConnections()).resolves.toEqual([
toStableSavedRemoteConnection(managedConnection),
]);
});
it("preserves secure-storage read failures with operation and key context", async () => {
const cause = new Error("keychain unavailable");
mocks.getItemAsync.mockRejectedValueOnce(cause);
await expect(loadSavedConnections()).rejects.toMatchObject({
_tag: "MobileSecureStorageError",
operation: "read",
key: "t3code.connections",
cause,
message: "Mobile secure storage operation read failed for key t3code.connections.",
});
});
it("logs structured decode failures before using the empty fallback", async () => {
await mocks.setItemAsync("t3code.connections", "{");
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
await expect(loadSavedConnections()).resolves.toEqual([]);
expect(warn).toHaveBeenCalledWith(
"[mobile-storage] ignored invalid JSON",
expect.objectContaining({
_tag: "MobileStorageDecodeError",
key: "t3code.connections",
cause: expect.any(SyntaxError),
message: "Failed to decode mobile storage value for key t3code.connections.",
}),
);
warn.mockRestore();
});
});