| @@ -2,6 +2,7 @@ import { createClerkClient, verifyToken } from "@clerk/backend"; · 1 unmodified line |
| 2 | import { describe, expect, it } from "@effect/vitest"; | 2 | import { describe, expect, it } from "@effect/vitest"; |
| 3 | import { vi } from "vite-plus/test"; | 3 | import { vi } from "vite-plus/test"; |
| 4 | import * as Context from "effect/Context"; | 4 | import * as Context from "effect/Context"; |
| | 5 | import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; |
| 5 | import * as Duration from "effect/Duration"; | 6 | import * as Duration from "effect/Duration"; |
| 6 | import * as Effect from "effect/Effect"; | 7 | import * as Effect from "effect/Effect"; |
| 7 | import * as Fiber from "effect/Fiber"; | 8 | import * as Fiber from "effect/Fiber"; |
| @@ -14,11 +15,21 @@ import * as Tracer from "effect/Tracer"; · 6 unmodified lines |
| 14 | import * as HttpRouter from "effect/unstable/http/HttpRouter"; | 15 | import * as HttpRouter from "effect/unstable/http/HttpRouter"; |
| 15 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; | 16 | import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; |
| 16 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; | 17 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; |
| | 18 | import * as HttpServer from "effect/unstable/http/HttpServer"; |
| | 19 | import * as HttpApiTest from "effect/unstable/httpapi/HttpApiTest"; |
| | 20 | import * as HttpApi from "effect/unstable/httpapi/HttpApi"; |
| 17 | import { EnvironmentId } from "@t3tools/contracts"; | 21 | import { EnvironmentId } from "@t3tools/contracts"; |
| 18 | import { RelayEnvironmentAuth } from "@t3tools/contracts/relay"; | 22 | import { |
| | 23 | RelayApi, |
| | 24 | RelayClientAuth, |
| | 25 | RelayClientPrincipal, |
| | 26 | RelayEnvironmentAuth, |
| | 27 | type RelayClientDeviceRecord, |
| | 28 | } from "@t3tools/contracts/relay"; |
| 19 | | 29 | |
| 20 | import { | 30 | import { |
| 21 | RELAY_REQUEST_DEADLINE_MS, | 31 | RELAY_REQUEST_DEADLINE_MS, |
| | 32 | clientApi, |
| 22 | relayCors, | 33 | relayCors, |
| 23 | relayDocsRedirectRoute, | 34 | relayDocsRedirectRoute, |
| 24 | relayEnvironmentAuthLayer, | 35 | relayEnvironmentAuthLayer, |
| @@ -35,6 +46,9 @@ import * as RelayDb from "../db.ts"; · 10 unmodified lines |
| 35 | import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts"; | 46 | import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts"; |
| 36 | import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts"; | 47 | import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts"; |
| 37 | import * as ManagedEndpointProvider from "../environments/ManagedEndpointProvider.ts"; | 48 | import * as ManagedEndpointProvider from "../environments/ManagedEndpointProvider.ts"; |
| | 49 | import * as EnvironmentLinker from "../environments/EnvironmentLinker.ts"; |
| | 50 | import * as RelayTokens from "../auth/RelayTokens.ts"; |
| | 51 | import * as Devices from "../agentActivity/Devices.ts"; |
| 38 | | 52 | |
| 39 | vi.mock("@clerk/backend", () => ({ | 53 | vi.mock("@clerk/backend", () => ({ |
| 40 | createClerkClient: vi.fn(), | 54 | createClerkClient: vi.fn(), |
| @@ -60,6 +74,79 @@ const relaySettings: RelayConfiguration.RelayConfiguration["Service"] = { · 19 unmodified lines |
| 60 | managedEndpointNamespace: undefined, | 74 | managedEndpointNamespace: undefined, |
| 61 | }; | 75 | }; |
| 62 | | 76 | |
| | 77 | describe("device listing compatibility", () => { |
| | 78 | it.effect("keeps v1 iOS-only while v2 returns every platform for the same account", () => { |
| | 79 | const iphone: RelayClientDeviceRecord = { |
| | 80 | deviceId: "iphone", |
| | 81 | label: "iPhone", |
| | 82 | platform: "ios", |
| | 83 | iosMajorVersion: 18, |
| | 84 | appVersion: "1.0.0", |
| | 85 | notifications: { |
| | 86 | enabled: true, |
| | 87 | notifyOnApproval: true, |
| | 88 | notifyOnInput: true, |
| | 89 | notifyOnCompletion: true, |
| | 90 | notifyOnFailure: true, |
| | 91 | }, |
| | 92 | liveActivities: { enabled: true }, |
| | 93 | updatedAt: "2026-09-09T00:00:00.000Z", |
| | 94 | }; |
| | 95 | const android: RelayClientDeviceRecord = { |
| | 96 | ...iphone, |
| | 97 | deviceId: "android", |
| | 98 | label: "Android", |
| | 99 | platform: "android", |
| | 100 | iosMajorVersion: null, |
| | 101 | androidApiLevel: 36, |
| | 102 | }; |
| | 103 | const handlers = clientApi.pipe( |
| | 104 | HttpRouter.provideRequest( |
| | 105 | Layer.mergeAll( |
| | 106 | Layer.mock(EnvironmentCredentials.EnvironmentCredentials, {}), |
| | 107 | Layer.mock(EnvironmentLinks.EnvironmentLinks, {}), |
| | 108 | Layer.mock(ManagedEndpointProvider.ManagedEndpointProvider, {}), |
| | 109 | Layer.mock(RelayDb.RelayTransactions, {}), |
| | 110 | ), |
| | 111 | ), |
| | 112 | Layer.provide( |
| | 113 | Layer.mergeAll( |
| | 114 | Layer.succeed(RelayConfiguration.RelayConfiguration, relaySettings), |
| | 115 | NodeCrypto.layer, |
| | 116 | Layer.mock(RelayTokens.RelayTokens, { resolveDpopAccessTokenScopes: () => null }), |
| | 117 | Layer.mock(EnvironmentLinker.EnvironmentLinker, {}), |
| | 118 | Layer.mock(EnvironmentLinks.EnvironmentLinks, {}), |
| | 119 | Layer.mock(ManagedEndpointProvider.ManagedEndpointProvider, {}), |
| | 120 | Layer.mock(Devices.Devices, { |
| | 121 | listForUser: ({ userId }) => { |
| | 122 | expect(userId).toBe("user-1"); |
| | 123 | return Effect.succeed([iphone, android]); |
| | 124 | }, |
| | 125 | }), |
| | 126 | ), |
| | 127 | ), |
| | 128 | Layer.provideMerge( |
| | 129 | Layer.succeed(RelayClientAuth, { |
| | 130 | clientBearer: (effect) => |
| | 131 | Effect.provideService(effect, RelayClientPrincipal, { |
| | 132 | userId: "user-1", |
| | 133 | token: "test-token", |
| | 134 | }), |
| | 135 | }), |
| | 136 | ), |
| | 137 | ); |
| | 138 | return Effect.gen(function* () { |
| | 139 | const client = yield* HttpApiTest.groups( |
| | 140 | HttpApi.make("RelayApi").add(RelayApi.groups.client), |
| | 141 | ["client"], |
| | 142 | ); |
| | 143 | const request = { headers: { authorization: "Bearer test-token" } }; |
| | 144 | expect(yield* client.client.listDevices(request)).toEqual({ devices: [iphone] }); |
| | 145 | expect(yield* client.client.listDevicesV2(request)).toEqual({ devices: [iphone, android] }); |
| | 146 | }).pipe(Effect.provide(Layer.mergeAll(handlers, HttpServer.layerServices)), Effect.scoped); |
| | 147 | }); |
| | 148 | }); |
| | 149 | |
| 63 | describe("relay client authentication", () => { | 150 | describe("relay client authentication", () => { |
| 64 | it.effect("preserves the existing Clerk session JWT path", () => | 151 | it.effect("preserves the existing Clerk session JWT path", () => |
| 65 | Effect.gen(function* () { | 152 | Effect.gen(function* () { |