























@@ -187,6 +187,48 @@ function expectQueuedAction(
187187return expectRecordFields(actions[0], "queued action", expected);
188188}
189189190+function expectWakeSendError(wake: unknown, reason: string, status: number) {
191+expectRecordFields(wake, "wake result", {
192+available: true,
193+throttled: false,
194+path: "send-error",
195+apnsReason: reason,
196+apnsStatus: status,
197+});
198+}
199+200+function expectNoAuthWake(wake: unknown, label: string, reason: string) {
201+expectRecordFields(wake, label, {
202+available: false,
203+throttled: false,
204+path: "no-auth",
205+apnsReason: reason,
206+});
207+}
208+209+async function expectWakeState(
210+nodeId: string,
211+expected: Record<string, unknown>,
212+label = "wake result",
213+) {
214+expectRecordFields(await maybeWakeNodeWithApns(nodeId), label, expected);
215+}
216+217+async function expectNudgeState(nodeId: string, expected: Record<string, unknown>) {
218+expectRecordFields(await maybeSendNodeWakeNudge(nodeId), "nudge result", expected);
219+}
220+221+async function expectWakeAndNudgeSent(nodeId: string) {
222+await expectWakeState(nodeId, {
223+path: "sent",
224+throttled: false,
225+});
226+await expectNudgeState(nodeId, {
227+sent: true,
228+throttled: false,
229+});
230+}
231+190232const WAKE_WAIT_TIMEOUT_MS = 3_001;
191233const DEFAULT_RELAY_CONFIG = {
192234baseUrl: "https://relay.example.com",
@@ -341,6 +383,34 @@ function createNodeClient(nodeId: string, commands?: string[]) {
341383};
342384}
343385386+function createForegroundUnavailableNodeRegistry(params: {
387+nodeId: string;
388+commands: string[];
389+platform: string;
390+}) {
391+return {
392+get: vi.fn(() => ({
393+nodeId: params.nodeId,
394+commands: params.commands,
395+platform: params.platform,
396+})),
397+invoke: vi.fn().mockResolvedValue({
398+ok: false,
399+error: {
400+code: "NODE_BACKGROUND_UNAVAILABLE",
401+message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground",
402+},
403+}),
404+};
405+}
406+407+function createMissingNodeRegistry() {
408+return {
409+get: vi.fn(() => undefined),
410+invoke: vi.fn().mockResolvedValue({ ok: true }),
411+};
412+}
413+344414async function pullPending(nodeId: string, commands?: string[]) {
345415const respond = vi.fn();
346416await nodeHandlers["node.pending.pull"]({
@@ -508,10 +578,7 @@ describe("node.invoke APNs wake path", () => {
508578it("keeps the existing not-connected response when wake path is unavailable", async () => {
509579mocks.loadApnsRegistration.mockResolvedValue(null);
510580511-const nodeRegistry = {
512-get: vi.fn(() => undefined),
513-invoke: vi.fn().mockResolvedValue({ ok: true }),
514-};
581+const nodeRegistry = createMissingNodeRegistry();
515582516583const respond = await invokeNode({ nodeRegistry });
517584const call = firstRespondCall(respond);
@@ -532,18 +599,8 @@ describe("node.invoke APNs wake path", () => {
532599const first = await maybeWakeNodeWithApns("ios-node-relay-no-auth");
533600const second = await maybeWakeNodeWithApns("ios-node-relay-no-auth");
534601535-expectRecordFields(first, "first wake result", {
536-available: false,
537-throttled: false,
538-path: "no-auth",
539-apnsReason: "relay config missing",
540-});
541-expectRecordFields(second, "second wake result", {
542-available: false,
543-throttled: false,
544-path: "no-auth",
545-apnsReason: "relay config missing",
546-});
602+expectNoAuthWake(first, "first wake result", "relay config missing");
603+expectNoAuthWake(second, "second wake result", "relay config missing");
547604expect(mocks.resolveApnsRelayConfigFromEnv).toHaveBeenCalledTimes(2);
548605expect(mocks.sendApnsBackgroundWake).not.toHaveBeenCalled();
549606});
@@ -559,33 +616,19 @@ describe("node.invoke APNs wake path", () => {
559616transport: "direct",
560617});
561618562-expectRecordFields(await maybeWakeNodeWithApns("ios-node-clear-wake"), "wake result", {
563-path: "sent",
564-throttled: false,
565-});
566-expectRecordFields(await maybeSendNodeWakeNudge("ios-node-clear-wake"), "nudge result", {
567-sent: true,
568-throttled: false,
569-});
570-expectRecordFields(await maybeWakeNodeWithApns("ios-node-clear-wake"), "wake result", {
619+await expectWakeAndNudgeSent("ios-node-clear-wake");
620+await expectWakeState("ios-node-clear-wake", {
571621path: "throttled",
572622throttled: true,
573623});
574-expectRecordFields(await maybeSendNodeWakeNudge("ios-node-clear-wake"), "nudge result", {
624+await expectNudgeState("ios-node-clear-wake", {
575625sent: false,
576626throttled: true,
577627});
578628579629clearNodeWakeState("ios-node-clear-wake");
580630581-expectRecordFields(await maybeWakeNodeWithApns("ios-node-clear-wake"), "wake result", {
582-path: "sent",
583-throttled: false,
584-});
585-expectRecordFields(await maybeSendNodeWakeNudge("ios-node-clear-wake"), "nudge result", {
586-sent: true,
587-throttled: false,
588-});
631+await expectWakeAndNudgeSent("ios-node-clear-wake");
589632expect(mocks.sendApnsBackgroundWake).toHaveBeenCalledTimes(2);
590633expect(mocks.sendApnsAlert).toHaveBeenCalledTimes(2);
591634});
@@ -720,13 +763,7 @@ describe("node.invoke APNs wake path", () => {
720763mocks.shouldClearStoredApnsRegistration.mockReturnValue(true);
721764const wake = await maybeWakeNodeWithApns("ios-node-stale", { force: true });
722765723-expectRecordFields(wake, "wake result", {
724-available: true,
725-throttled: false,
726-path: "send-error",
727-apnsReason: "BadDeviceToken",
728-apnsStatus: 400,
729-});
766+expectWakeSendError(wake, "BadDeviceToken", 400);
730767expect(mocks.clearApnsRegistrationIfCurrent).toHaveBeenCalledWith({
731768nodeId: "ios-node-stale",
732769 registration,
@@ -743,13 +780,7 @@ describe("node.invoke APNs wake path", () => {
743780mocks.shouldClearStoredApnsRegistration.mockReturnValue(false);
744781const wake = await maybeWakeNodeWithApns("ios-node-relay", { force: true });
745782746-expectRecordFields(wake, "wake result", {
747-available: true,
748-throttled: false,
749-path: "send-error",
750-apnsReason: "Unregistered",
751-apnsStatus: 410,
752-});
783+expectWakeSendError(wake, "Unregistered", 410);
753784expect(mocks.resolveApnsRelayConfigFromEnv).toHaveBeenCalledWith(
754785process.env,
755786{
@@ -780,10 +811,7 @@ describe("node.invoke APNs wake path", () => {
780811vi.useFakeTimers();
781812mockDirectWakeConfig("ios-node-throttle");
782813783-const nodeRegistry = {
784-get: vi.fn(() => undefined),
785-invoke: vi.fn().mockResolvedValue({ ok: true }),
786-};
814+const nodeRegistry = createMissingNodeRegistry();
787815788816const invokePromise = invokeNode({
789817 nodeRegistry,
@@ -799,20 +827,11 @@ describe("node.invoke APNs wake path", () => {
799827it("queues iOS foreground-only command failures and keeps them until acked", async () => {
800828mocks.loadApnsRegistration.mockResolvedValue(null);
801829802-const nodeRegistry = {
803-get: vi.fn(() => ({
804-nodeId: "ios-node-queued",
805-commands: ["canvas.navigate"],
806-platform: "iOS 26.4.0",
807-})),
808-invoke: vi.fn().mockResolvedValue({
809-ok: false,
810-error: {
811-code: "NODE_BACKGROUND_UNAVAILABLE",
812-message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground",
813-},
814-}),
815-};
830+const nodeRegistry = createForegroundUnavailableNodeRegistry({
831+nodeId: "ios-node-queued",
832+commands: ["canvas.navigate"],
833+platform: "iOS 26.4.0",
834+});
816835817836const respond = await invokeNode({
818837 nodeRegistry,
@@ -893,20 +912,11 @@ describe("node.invoke APNs wake path", () => {
893912},
894913);
895914896-const nodeRegistry = {
897-get: vi.fn(() => ({
898-nodeId: "ios-node-policy",
899-commands: ["camera.snap", "canvas.navigate"],
900-platform: "iOS 26.4.0",
901-})),
902-invoke: vi.fn().mockResolvedValue({
903-ok: false,
904-error: {
905-code: "NODE_BACKGROUND_UNAVAILABLE",
906-message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground",
907-},
908-}),
909-};
915+const nodeRegistry = createForegroundUnavailableNodeRegistry({
916+nodeId: "ios-node-policy",
917+commands: ["camera.snap", "canvas.navigate"],
918+platform: "iOS 26.4.0",
919+});
910920911921await invokeNode({
912922 nodeRegistry,
@@ -945,40 +955,24 @@ describe("node.invoke APNs wake path", () => {
945955it("dedupes queued foreground actions by idempotency key", async () => {
946956mocks.loadApnsRegistration.mockResolvedValue(null);
947957948-const nodeRegistry = {
949-get: vi.fn(() => ({
950-nodeId: "ios-node-dedupe",
951-commands: ["canvas.navigate"],
952-platform: "iPadOS 26.4.0",
953-})),
954-invoke: vi.fn().mockResolvedValue({
955-ok: false,
956-error: {
957-code: "NODE_BACKGROUND_UNAVAILABLE",
958-message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground",
959-},
960-}),
961-};
962-963-await invokeNode({
964- nodeRegistry,
965-requestParams: {
966-nodeId: "ios-node-dedupe",
967-command: "canvas.navigate",
968-params: { url: "http://example.com/first" },
969-idempotencyKey: "idem-dedupe",
970-},
971-});
972-await invokeNode({
973- nodeRegistry,
974-requestParams: {
975-nodeId: "ios-node-dedupe",
976-command: "canvas.navigate",
977-params: { url: "http://example.com/first" },
978-idempotencyKey: "idem-dedupe",
979-},
958+const nodeRegistry = createForegroundUnavailableNodeRegistry({
959+nodeId: "ios-node-dedupe",
960+commands: ["canvas.navigate"],
961+platform: "iPadOS 26.4.0",
980962});
981963964+for (let attempt = 0; attempt < 2; attempt += 1) {
965+await invokeNode({
966+ nodeRegistry,
967+requestParams: {
968+nodeId: "ios-node-dedupe",
969+command: "canvas.navigate",
970+params: { url: "http://example.com/first" },
971+idempotencyKey: "idem-dedupe",
972+},
973+});
974+}
975+982976const pullRespond = await pullPending("ios-node-dedupe", ["canvas.navigate"]);
983977const pullCall = firstRespondCall(pullRespond);
984978const pullPayload = requireRespondPayload(pullCall, "pull response");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。