mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:48:38 +00:00
test: extract apns store coverage
This commit is contained in:
308
src/infra/push-apns.store.test.ts
Normal file
308
src/infra/push-apns.store.test.ts
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
clearApnsRegistration,
|
||||||
|
clearApnsRegistrationIfCurrent,
|
||||||
|
loadApnsRegistration,
|
||||||
|
registerApnsRegistration,
|
||||||
|
registerApnsToken,
|
||||||
|
} from "./push-apns.js";
|
||||||
|
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
async function makeTempDir(): Promise<string> {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-push-apns-store-test-"));
|
||||||
|
tempDirs.push(dir);
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
while (tempDirs.length > 0) {
|
||||||
|
const dir = tempDirs.pop();
|
||||||
|
if (dir) {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("push APNs registration store", () => {
|
||||||
|
it("stores and reloads direct APNs registrations", async () => {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
const saved = await registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "sandbox",
|
||||||
|
baseDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const loaded = await loadApnsRegistration("ios-node-1", baseDir);
|
||||||
|
expect(loaded).toMatchObject({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
transport: "direct",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "sandbox",
|
||||||
|
updatedAtMs: saved.updatedAtMs,
|
||||||
|
});
|
||||||
|
expect(loaded && loaded.transport === "direct" ? loaded.token : null).toBe(
|
||||||
|
"abcd1234abcd1234abcd1234abcd1234",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores relay-backed registrations without a raw token", async () => {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
const saved = await registerApnsRegistration({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "official",
|
||||||
|
tokenDebugSuffix: " abcd-1234 ",
|
||||||
|
baseDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const loaded = await loadApnsRegistration("ios-node-relay", baseDir);
|
||||||
|
expect(saved.transport).toBe("relay");
|
||||||
|
expect(loaded).toMatchObject({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "official",
|
||||||
|
tokenDebugSuffix: "abcd1234",
|
||||||
|
});
|
||||||
|
expect(loaded && "token" in loaded).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes legacy direct records from disk and ignores invalid entries", async () => {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
const statePath = path.join(baseDir, "push", "apns-registrations.json");
|
||||||
|
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
statePath,
|
||||||
|
`${JSON.stringify(
|
||||||
|
{
|
||||||
|
registrationsByNodeId: {
|
||||||
|
" ios-node-legacy ": {
|
||||||
|
nodeId: " ios-node-legacy ",
|
||||||
|
token: "<ABCD1234ABCD1234ABCD1234ABCD1234>",
|
||||||
|
topic: " ai.openclaw.ios ",
|
||||||
|
environment: " PRODUCTION ",
|
||||||
|
updatedAtMs: 3,
|
||||||
|
},
|
||||||
|
" ": {
|
||||||
|
nodeId: " ios-node-fallback ",
|
||||||
|
token: "<ABCD1234ABCD1234ABCD1234ABCD1234>",
|
||||||
|
topic: " ai.openclaw.ios ",
|
||||||
|
updatedAtMs: 2,
|
||||||
|
},
|
||||||
|
"ios-node-bad-relay": {
|
||||||
|
transport: "relay",
|
||||||
|
nodeId: "ios-node-bad-relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "beta",
|
||||||
|
updatedAtMs: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)}\n`,
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(loadApnsRegistration("ios-node-legacy", baseDir)).resolves.toMatchObject({
|
||||||
|
nodeId: "ios-node-legacy",
|
||||||
|
transport: "direct",
|
||||||
|
token: "abcd1234abcd1234abcd1234abcd1234",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
updatedAtMs: 3,
|
||||||
|
});
|
||||||
|
await expect(loadApnsRegistration("ios-node-fallback", baseDir)).resolves.toMatchObject({
|
||||||
|
nodeId: "ios-node-fallback",
|
||||||
|
transport: "direct",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "sandbox",
|
||||||
|
updatedAtMs: 2,
|
||||||
|
});
|
||||||
|
await expect(loadApnsRegistration("ios-node-bad-relay", baseDir)).resolves.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back cleanly for malformed or missing registration state", async () => {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
const statePath = path.join(baseDir, "push", "apns-registrations.json");
|
||||||
|
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
||||||
|
await fs.writeFile(statePath, "[]", "utf8");
|
||||||
|
|
||||||
|
await expect(loadApnsRegistration("ios-node-missing", baseDir)).resolves.toBeNull();
|
||||||
|
await expect(loadApnsRegistration(" ", baseDir)).resolves.toBeNull();
|
||||||
|
await expect(clearApnsRegistration(" ", baseDir)).resolves.toBe(false);
|
||||||
|
await expect(clearApnsRegistration("ios-node-missing", baseDir)).resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects invalid direct and relay registration inputs", async () => {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
const oversized = "x".repeat(257);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "not-a-token",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("invalid APNs token");
|
||||||
|
await expect(
|
||||||
|
registerApnsToken({
|
||||||
|
nodeId: "n".repeat(257),
|
||||||
|
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("nodeId required");
|
||||||
|
await expect(
|
||||||
|
registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "A".repeat(513),
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("invalid APNs token");
|
||||||
|
await expect(
|
||||||
|
registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
||||||
|
topic: "a".repeat(256),
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("topic required");
|
||||||
|
await expect(
|
||||||
|
registerApnsRegistration({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "staging",
|
||||||
|
distribution: "official",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("relay registrations must use production environment");
|
||||||
|
await expect(
|
||||||
|
registerApnsRegistration({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "beta",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("relay registrations must use official distribution");
|
||||||
|
await expect(
|
||||||
|
registerApnsRegistration({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: oversized,
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "official",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("relayHandle too long");
|
||||||
|
await expect(
|
||||||
|
registerApnsRegistration({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "send-grant-123",
|
||||||
|
installationId: oversized,
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "official",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("installationId too long");
|
||||||
|
await expect(
|
||||||
|
registerApnsRegistration({
|
||||||
|
nodeId: "ios-node-relay",
|
||||||
|
transport: "relay",
|
||||||
|
relayHandle: "relay-handle-123",
|
||||||
|
sendGrant: "x".repeat(1025),
|
||||||
|
installationId: "install-123",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "production",
|
||||||
|
distribution: "official",
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("sendGrant too long");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("persists with a trailing newline and clears registrations", async () => {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
await registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
baseDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const statePath = path.join(baseDir, "push", "apns-registrations.json");
|
||||||
|
await expect(fs.readFile(statePath, "utf8")).resolves.toMatch(/\n$/);
|
||||||
|
await expect(clearApnsRegistration("ios-node-1", baseDir)).resolves.toBe(true);
|
||||||
|
await expect(loadApnsRegistration("ios-node-1", baseDir)).resolves.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("only clears a registration when the stored entry still matches", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
try {
|
||||||
|
const baseDir = await makeTempDir();
|
||||||
|
vi.setSystemTime(new Date("2026-03-11T00:00:00Z"));
|
||||||
|
const stale = await registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "sandbox",
|
||||||
|
baseDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.setSystemTime(new Date("2026-03-11T00:00:01Z"));
|
||||||
|
const fresh = await registerApnsToken({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
||||||
|
topic: "ai.openclaw.ios",
|
||||||
|
environment: "sandbox",
|
||||||
|
baseDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
clearApnsRegistrationIfCurrent({
|
||||||
|
nodeId: "ios-node-1",
|
||||||
|
registration: stale,
|
||||||
|
baseDir,
|
||||||
|
}),
|
||||||
|
).resolves.toBe(false);
|
||||||
|
await expect(loadApnsRegistration("ios-node-1", baseDir)).resolves.toEqual(fresh);
|
||||||
|
} finally {
|
||||||
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,27 +1,10 @@
|
|||||||
import { generateKeyPairSync } from "node:crypto";
|
import { generateKeyPairSync } from "node:crypto";
|
||||||
import fs from "node:fs/promises";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import {
|
import { sendApnsAlert, sendApnsBackgroundWake } from "./push-apns.js";
|
||||||
clearApnsRegistration,
|
|
||||||
clearApnsRegistrationIfCurrent,
|
|
||||||
loadApnsRegistration,
|
|
||||||
registerApnsRegistration,
|
|
||||||
registerApnsToken,
|
|
||||||
sendApnsAlert,
|
|
||||||
sendApnsBackgroundWake,
|
|
||||||
} from "./push-apns.js";
|
|
||||||
|
|
||||||
const tempDirs: string[] = [];
|
|
||||||
const testAuthPrivateKey = generateKeyPairSync("ec", { namedCurve: "prime256v1" })
|
const testAuthPrivateKey = generateKeyPairSync("ec", { namedCurve: "prime256v1" })
|
||||||
.privateKey.export({ format: "pem", type: "pkcs8" })
|
.privateKey.export({ format: "pem", type: "pkcs8" })
|
||||||
.toString();
|
.toString();
|
||||||
async function makeTempDir(): Promise<string> {
|
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-push-apns-test-"));
|
|
||||||
tempDirs.push(dir);
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDirectApnsSendFixture(params: {
|
function createDirectApnsSendFixture(params: {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@@ -48,229 +31,6 @@ function createDirectApnsSendFixture(params: {
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
while (tempDirs.length > 0) {
|
|
||||||
const dir = tempDirs.pop();
|
|
||||||
if (dir) {
|
|
||||||
await fs.rm(dir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("push APNs registration store", () => {
|
|
||||||
it("stores and reloads node APNs registration", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
const saved = await registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "sandbox",
|
|
||||||
baseDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
const loaded = await loadApnsRegistration("ios-node-1", baseDir);
|
|
||||||
expect(loaded).not.toBeNull();
|
|
||||||
expect(loaded?.nodeId).toBe("ios-node-1");
|
|
||||||
expect(loaded?.transport).toBe("direct");
|
|
||||||
expect(loaded && loaded.transport === "direct" ? loaded.token : null).toBe(
|
|
||||||
"abcd1234abcd1234abcd1234abcd1234",
|
|
||||||
);
|
|
||||||
expect(loaded?.topic).toBe("ai.openclaw.ios");
|
|
||||||
expect(loaded?.environment).toBe("sandbox");
|
|
||||||
expect(loaded?.updatedAtMs).toBe(saved.updatedAtMs);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("stores and reloads relay-backed APNs registrations without a raw token", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
const saved = await registerApnsRegistration({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: "relay-handle-123",
|
|
||||||
sendGrant: "send-grant-123",
|
|
||||||
installationId: "install-123",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "production",
|
|
||||||
distribution: "official",
|
|
||||||
tokenDebugSuffix: "abcd1234",
|
|
||||||
baseDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
const loaded = await loadApnsRegistration("ios-node-relay", baseDir);
|
|
||||||
expect(saved.transport).toBe("relay");
|
|
||||||
expect(loaded).toMatchObject({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: "relay-handle-123",
|
|
||||||
sendGrant: "send-grant-123",
|
|
||||||
installationId: "install-123",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "production",
|
|
||||||
distribution: "official",
|
|
||||||
tokenDebugSuffix: "abcd1234",
|
|
||||||
});
|
|
||||||
expect(loaded && "token" in loaded).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects invalid APNs tokens", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
await expect(
|
|
||||||
registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "not-a-token",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("invalid APNs token");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects oversized direct APNs registration fields", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
await expect(
|
|
||||||
registerApnsToken({
|
|
||||||
nodeId: "n".repeat(257),
|
|
||||||
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("nodeId required");
|
|
||||||
await expect(
|
|
||||||
registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "A".repeat(513),
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("invalid APNs token");
|
|
||||||
await expect(
|
|
||||||
registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
|
||||||
topic: "a".repeat(256),
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("topic required");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects relay registrations that do not use production/official values", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
await expect(
|
|
||||||
registerApnsRegistration({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: "relay-handle-123",
|
|
||||||
sendGrant: "send-grant-123",
|
|
||||||
installationId: "install-123",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "staging",
|
|
||||||
distribution: "official",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("relay registrations must use production environment");
|
|
||||||
await expect(
|
|
||||||
registerApnsRegistration({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: "relay-handle-123",
|
|
||||||
sendGrant: "send-grant-123",
|
|
||||||
installationId: "install-123",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "production",
|
|
||||||
distribution: "beta",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("relay registrations must use official distribution");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects oversized relay registration identifiers", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
const oversized = "x".repeat(257);
|
|
||||||
await expect(
|
|
||||||
registerApnsRegistration({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: oversized,
|
|
||||||
sendGrant: "send-grant-123",
|
|
||||||
installationId: "install-123",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "production",
|
|
||||||
distribution: "official",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("relayHandle too long");
|
|
||||||
await expect(
|
|
||||||
registerApnsRegistration({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: "relay-handle-123",
|
|
||||||
sendGrant: "send-grant-123",
|
|
||||||
installationId: oversized,
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "production",
|
|
||||||
distribution: "official",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("installationId too long");
|
|
||||||
await expect(
|
|
||||||
registerApnsRegistration({
|
|
||||||
nodeId: "ios-node-relay",
|
|
||||||
transport: "relay",
|
|
||||||
relayHandle: "relay-handle-123",
|
|
||||||
sendGrant: "x".repeat(1025),
|
|
||||||
installationId: "install-123",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "production",
|
|
||||||
distribution: "official",
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow("sendGrant too long");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clears registrations", async () => {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
await registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
baseDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(clearApnsRegistration("ios-node-1", baseDir)).resolves.toBe(true);
|
|
||||||
await expect(loadApnsRegistration("ios-node-1", baseDir)).resolves.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("only clears a registration when the stored entry still matches", async () => {
|
|
||||||
vi.useFakeTimers();
|
|
||||||
try {
|
|
||||||
const baseDir = await makeTempDir();
|
|
||||||
vi.setSystemTime(new Date("2026-03-11T00:00:00Z"));
|
|
||||||
const stale = await registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "sandbox",
|
|
||||||
baseDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.setSystemTime(new Date("2026-03-11T00:00:01Z"));
|
|
||||||
const fresh = await registerApnsToken({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
|
|
||||||
topic: "ai.openclaw.ios",
|
|
||||||
environment: "sandbox",
|
|
||||||
baseDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
clearApnsRegistrationIfCurrent({
|
|
||||||
nodeId: "ios-node-1",
|
|
||||||
registration: stale,
|
|
||||||
baseDir,
|
|
||||||
}),
|
|
||||||
).resolves.toBe(false);
|
|
||||||
await expect(loadApnsRegistration("ios-node-1", baseDir)).resolves.toEqual(fresh);
|
|
||||||
} finally {
|
|
||||||
vi.useRealTimers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("push APNs send semantics", () => {
|
describe("push APNs send semantics", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user