| 1 | import { afterEach, describe, expect, it, vi } from "vite-plus/test"; |
| 2 | import { EnvironmentId } from "@t3tools/contracts"; |
| 3 | |
| 4 | import { isRelayManagedConnection, toStableSavedRemoteConnection } from "./connection"; |
| 5 | import { authClientMetadata } from "./authClientMetadata"; |
| 6 | |
| 7 | const mobilePlatform = vi.hoisted(() => ({ OS: "ios" as "ios" | "android" })); |
| 8 | const mobileDevice = vi.hoisted(() => ({ |
| 9 | deviceType: 1, |
| 10 | DeviceType: { |
| 11 | UNKNOWN: 0, |
| 12 | PHONE: 1, |
| 13 | TABLET: 2, |
| 14 | DESKTOP: 3, |
| 15 | TV: 4, |
| 16 | }, |
| 17 | })); |
| 18 | |
| 19 | vi.mock("./runtime", () => ({ |
| 20 | runtime: { |
| 21 | runPromise: vi.fn(), |
| 22 | }, |
| 23 | })); |
| 24 | |
| 25 | vi.mock("react-native", () => ({ |
| 26 | Platform: mobilePlatform, |
| 27 | })); |
| 28 | |
| 29 | vi.mock("expo-device", () => mobileDevice); |
| 30 | |
| 31 | describe("mobile remote connection records", () => { |
| 32 | afterEach(() => { |
| 33 | mobilePlatform.OS = "ios"; |
| 34 | mobileDevice.deviceType = mobileDevice.DeviceType.PHONE; |
| 35 | }); |
| 36 | |
| 37 | it("identifies mobile token exchanges for authorized-client presentation", () => { |
| 38 | expect(authClientMetadata()).toEqual({ |
| 39 | label: "The Knife Mobile", |
| 40 | deviceType: "mobile", |
| 41 | os: "iOS", |
| 42 | surface: "mobile", |
| 43 | }); |
| 44 | }); |
| 45 | |
| 46 | it("identifies Android clients for authorized-client presentation", () => { |
| 47 | mobilePlatform.OS = "android"; |
| 48 | |
| 49 | expect(authClientMetadata()).toMatchObject({ |
| 50 | os: "Android", |
| 51 | }); |
| 52 | }); |
| 53 | |
| 54 | it("identifies native tablets separately from phones", () => { |
| 55 | mobileDevice.deviceType = mobileDevice.DeviceType.TABLET; |
| 56 | |
| 57 | expect(authClientMetadata()).toMatchObject({ |
| 58 | deviceType: "tablet", |
| 59 | os: "iOS", |
| 60 | }); |
| 61 | }); |
| 62 | |
| 63 | it("includes the mobile app version when the client provides it", () => { |
| 64 | expect(authClientMetadata("1.2.3")).toMatchObject({ |
| 65 | surface: "mobile", |
| 66 | appVersion: "1.2.3", |
| 67 | }); |
| 68 | }); |
| 69 | |
| 70 | it("recognizes explicitly managed relay connections", () => { |
| 71 | expect(isRelayManagedConnection({ relayManaged: true })).toBe(true); |
| 72 | }); |
| 73 | |
| 74 | it("keeps existing DPoP tunnel records read-only after upgrading", () => { |
| 75 | expect(isRelayManagedConnection({ authenticationMethod: "dpop" })).toBe(true); |
| 76 | expect(isRelayManagedConnection({ authenticationMethod: "bearer" })).toBe(false); |
| 77 | }); |
| 78 | |
| 79 | it("drops short-lived managed environment credentials from stable records", () => { |
| 80 | const connection = { |
| 81 | environmentId: EnvironmentId.make("environment-1"), |
| 82 | environmentLabel: "Desktop", |
| 83 | pairingUrl: "https://desktop.example/", |
| 84 | displayUrl: "https://desktop.example/", |
| 85 | httpBaseUrl: "https://desktop.example/", |
| 86 | wsBaseUrl: "wss://desktop.example/", |
| 87 | bearerToken: null, |
| 88 | authenticationMethod: "dpop", |
| 89 | dpopAccessToken: "short-lived-token", |
| 90 | relayManaged: true, |
| 91 | } as const; |
| 92 | |
| 93 | expect(toStableSavedRemoteConnection(connection)).not.toHaveProperty("dpopAccessToken"); |
| 94 | }); |
| 95 | }); |