| ... | ... | @@ -38,6 +38,7 @@ import { |
| 38 | 38 | decodeConfirmedOrigin, |
| 39 | 39 | decodeRuntimeConfig, |
| 40 | 40 | RELAY_ENVIRONMENT_CREDENTIAL_SECRET, |
| 41 | RELAY_ISSUER_SECRET, |
| 41 | 42 | RELAY_URL_SECRET, |
| 42 | 43 | } from "./config.ts"; |
| 43 | 44 | import * as CloudLink from "./CloudLink.ts"; |
| ... | ... | @@ -1158,4 +1159,59 @@ describe("releaseManagedTunnelOnShutdown", () => { |
| 1158 | 1159 | }), |
| 1159 | 1160 | ); |
| 1160 | 1161 | }); |
| 1162 | |
| 1163 | // The relay commits the link before the server stores it (#11898). A write |
| 1164 | // that fails part-way must not leave the stale tunnel config that reports |
| 1165 | // the link as managed, or Settings never relinks. |
| 1166 | it.effect("does not report a managed tunnel after a relay config write fails part-way", () => { |
| 1167 | const { store, values } = makeMemorySecretStore([ |
| 1168 | [CLOUD_ENDPOINT_RUNTIME_CONFIG, "managed-config"], |
| 1169 | [CLOUD_ENDPOINT_CONFIRMED_ORIGIN, "confirmed-origin"], |
| 1170 | [RELAY_URL_SECRET, "https://relay.example.test"], |
| 1171 | [CLOUD_LINKED_USER_ID, "user-123"], |
| 1172 | [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "old-credential"], |
| 1173 | ]); |
| 1174 | const failingStore: ServerSecretStore.ServerSecretStore["Service"] = { |
| 1175 | ...store, |
| 1176 | set: (name, value) => |
| 1177 | name === RELAY_ISSUER_SECRET |
| 1178 | ? Effect.fail( |
| 1179 | new ServerSecretStore.SecretStorePersistError({ |
| 1180 | resource: name, |
| 1181 | cause: PlatformError.systemError({ |
| 1182 | _tag: "PermissionDenied", |
| 1183 | module: "FileSystem", |
| 1184 | method: "rename", |
| 1185 | pathOrDescriptor: `${name}.bin`, |
| 1186 | }), |
| 1187 | }), |
| 1188 | ) |
| 1189 | : store.set(name, value), |
| 1190 | }; |
| 1191 | const applyConfigCalls: Array<unknown> = []; |
| 1192 | const requests: Array<HttpClientRequest.HttpClientRequest> = []; |
| 1193 | const cloudMintPublicKey = |
| 1194 | "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAMY09Vo5CzEA6aEkhB/0h27t4Qs71SHS1fTSI4K0KvZQ=\n-----END PUBLIC KEY-----\n"; |
| 1195 | |
| 1196 | return Effect.gen(function* () { |
| 1197 | const link = yield* CloudLink.CloudLink; |
| 1198 | const error = yield* Effect.flip( |
| 1199 | link.applyRelayConfig({ |
| 1200 | relayUrl: "https://relay.example.test", |
| 1201 | cloudUserId: "user-123", |
| 1202 | environmentCredential: "new-credential", |
| 1203 | cloudMintPublicKey, |
| 1204 | endpointRuntime: null, |
| 1205 | }), |
| 1206 | ); |
| 1207 | |
| 1208 | expect(error._tag).toBe("CloudLinkInternalError"); |
| 1209 | expect(yield* link.linkState()).toMatchObject({ |
| 1210 | linked: true, |
| 1211 | cloudUserId: "user-123", |
| 1212 | managedTunnelActive: false, |
| 1213 | }); |
| 1214 | expect(values.has(CLOUD_ENDPOINT_RUNTIME_CONFIG)).toBe(false); |
| 1215 | }).pipe(provideReleaseHarness({ store: failingStore, applyConfigCalls, requests })); |
| 1216 | }); |
| 1161 | 1217 | }); |