Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,271 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { type MobileSession, type SessionStorage } from "./auth.js";
|
||||
import { registerPushNotifications, type PushNotificationsBridge } from "./pushRegistration.js";
|
||||
|
||||
function createMemoryStorage(session: MobileSession | null): SessionStorage {
|
||||
return {
|
||||
async read() {
|
||||
return session;
|
||||
},
|
||||
async write(nextSession) {
|
||||
session = nextSession;
|
||||
},
|
||||
async clear() {
|
||||
session = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createFailingStorage(): SessionStorage {
|
||||
return {
|
||||
async read() {
|
||||
throw new Error("keychain locked");
|
||||
},
|
||||
async write() {
|
||||
/* noop */
|
||||
},
|
||||
async clear() {
|
||||
/* noop */
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createPushPlugin(
|
||||
options: {
|
||||
currentPermission?: "prompt" | "prompt-with-rationale" | "granted" | "denied";
|
||||
requestedPermission?: "prompt" | "prompt-with-rationale" | "granted" | "denied";
|
||||
token?: string;
|
||||
failRegistration?: boolean;
|
||||
} = {}
|
||||
): PushNotificationsBridge & { calls: string[] } {
|
||||
const calls: string[] = [];
|
||||
let registrationListener: ((token: { value: string }) => void) | null = null;
|
||||
let errorListener: ((error: unknown) => void) | null = null;
|
||||
|
||||
return {
|
||||
calls,
|
||||
async checkPermissions() {
|
||||
calls.push("checkPermissions");
|
||||
return { receive: options.currentPermission ?? "granted" };
|
||||
},
|
||||
async requestPermissions() {
|
||||
calls.push("requestPermissions");
|
||||
return { receive: options.requestedPermission ?? "granted" };
|
||||
},
|
||||
async register() {
|
||||
calls.push("register");
|
||||
queueMicrotask(() => {
|
||||
if (options.failRegistration) {
|
||||
errorListener?.(new Error("native registration failed"));
|
||||
return;
|
||||
}
|
||||
registrationListener?.({ value: options.token ?? "native-token-1" });
|
||||
});
|
||||
},
|
||||
async addListener(eventName, listenerFunc) {
|
||||
calls.push(`addListener:${eventName}`);
|
||||
if (eventName === "registration") {
|
||||
registrationListener = listenerFunc as (token: { value: string }) => void;
|
||||
} else {
|
||||
errorListener = listenerFunc as (error: unknown) => void;
|
||||
}
|
||||
return {
|
||||
async remove() {
|
||||
calls.push(`remove:${eventName}`);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("registerPushNotifications", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("keeps apiBaseUrl as a required caller contract", async () => {
|
||||
await expect(registerPushNotifications({ apiBaseUrl: "" })).rejects.toThrow("apiBaseUrl is required");
|
||||
});
|
||||
|
||||
it("skips defensively when running on web", async () => {
|
||||
const pushPlugin = createPushPlugin();
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "web",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: "t-1" }),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "web-platform" });
|
||||
expect(pushPlugin.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it("skips when no mobile session is stored", async () => {
|
||||
const pushPlugin = createPushPlugin();
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "ios",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage(null),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "no-session" });
|
||||
expect(pushPlugin.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it("skips when the stored session cannot be read", async () => {
|
||||
const pushPlugin = createPushPlugin();
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "ios",
|
||||
pushPlugin,
|
||||
storage: createFailingStorage(),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "no-session" });
|
||||
expect(pushPlugin.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it("skips unsupported native platforms before touching the push bridge", async () => {
|
||||
const pushPlugin = createPushPlugin();
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "electron" as "ios",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: null }),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "unsupported-platform" });
|
||||
expect(pushPlugin.calls).toEqual([]);
|
||||
});
|
||||
|
||||
it("requests permission and skips when the user denies push access", async () => {
|
||||
const pushPlugin = createPushPlugin({ currentPermission: "prompt", requestedPermission: "denied" });
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "android",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: null }),
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "permission-denied" });
|
||||
expect(pushPlugin.calls).toEqual(["checkPermissions", "requestPermissions"]);
|
||||
});
|
||||
|
||||
it("registers an iOS APNs token through the mobile API client", async () => {
|
||||
const requests: Array<{ url: string; authorization: string | null; body: unknown }> = [];
|
||||
const fakeFetch: typeof fetch = async (input, init) => {
|
||||
requests.push({
|
||||
url: String(input),
|
||||
authorization: new Headers(init?.headers).get("authorization"),
|
||||
body: init?.body ? JSON.parse(String(init.body)) : null,
|
||||
});
|
||||
return new Response(JSON.stringify({ success: true, data: { id: "dt-1" } }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
};
|
||||
|
||||
const pushPlugin = createPushPlugin({ currentPermission: "prompt", requestedPermission: "granted", token: " apns-token-1 " });
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test/",
|
||||
appVersion: "1.2.3",
|
||||
platform: "ios",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: "t-1" }),
|
||||
fetchImpl: fakeFetch,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "registered", platform: "ios", pushProvider: "apns" });
|
||||
expect(pushPlugin.calls).toEqual([
|
||||
"checkPermissions",
|
||||
"requestPermissions",
|
||||
"addListener:registration",
|
||||
"addListener:registrationError",
|
||||
"register",
|
||||
"remove:registration",
|
||||
"remove:registrationError",
|
||||
]);
|
||||
expect(requests).toEqual([
|
||||
{
|
||||
url: "https://api.example.test/v1/devices/register",
|
||||
authorization: "Bearer bearer-1",
|
||||
body: {
|
||||
token: "apns-token-1",
|
||||
userId: "u-1",
|
||||
tenantId: "t-1",
|
||||
platform: "ios",
|
||||
provider: "apns",
|
||||
pushProvider: "apns",
|
||||
appVersion: "1.2.3",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("registers Android with the FCM provider", async () => {
|
||||
const bodies: unknown[] = [];
|
||||
const fakeFetch: typeof fetch = async (_input, init) => {
|
||||
bodies.push(init?.body ? JSON.parse(String(init.body)) : null);
|
||||
return new Response(JSON.stringify({ success: true }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
};
|
||||
|
||||
const pushPlugin = createPushPlugin({ token: "fcm-token-1" });
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "android",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: null }),
|
||||
fetchImpl: fakeFetch,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "registered", platform: "android", pushProvider: "fcm" });
|
||||
expect(pushPlugin.calls).not.toContain("requestPermissions");
|
||||
expect(bodies).toEqual([
|
||||
{
|
||||
token: "fcm-token-1",
|
||||
userId: "u-1",
|
||||
tenantId: null,
|
||||
platform: "android",
|
||||
provider: "fcm",
|
||||
pushProvider: "fcm",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("skips when the native registration callback fails", async () => {
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "ios",
|
||||
pushPlugin: createPushPlugin({ failRegistration: true }),
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: "t-1" }),
|
||||
timeoutMs: 10,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "registration-failed" });
|
||||
});
|
||||
|
||||
it("skips when native registration returns an empty token", async () => {
|
||||
const pushPlugin = createPushPlugin({ token: " " });
|
||||
const result = await registerPushNotifications({
|
||||
apiBaseUrl: "https://api.example.test",
|
||||
platform: "ios",
|
||||
pushPlugin,
|
||||
storage: createMemoryStorage({ token: "bearer-1", userId: "u-1", tenantId: "t-1" }),
|
||||
timeoutMs: 10,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ status: "skipped", reason: "registration-failed" });
|
||||
expect(pushPlugin.calls).toEqual([
|
||||
"checkPermissions",
|
||||
"addListener:registration",
|
||||
"addListener:registrationError",
|
||||
"register",
|
||||
"remove:registration",
|
||||
"remove:registrationError",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user