| ... | @@ -6,7 +6,6 @@ import * as Deferred from "effect/Deferred"; | ... | @@ -6,7 +6,6 @@ import * as Deferred from "effect/Deferred"; |
| 6 | import * as Effect from "effect/Effect"; | 6 | import * as Effect from "effect/Effect"; |
| 7 | import * as Fiber from "effect/Fiber"; | 7 | import * as Fiber from "effect/Fiber"; |
| 8 | import * as Hex from "effect/encoding/Hex"; | 8 | import * as Hex from "effect/encoding/Hex"; |
| 9 | import * as Fiber from "effect/Fiber"; | | |
| 10 | import * as FileSystem from "effect/FileSystem"; | 9 | import * as FileSystem from "effect/FileSystem"; |
| 11 | import * as Layer from "effect/Layer"; | 10 | import * as Layer from "effect/Layer"; |
| 12 | import * as PlatformError from "effect/PlatformError"; | 11 | import * as PlatformError from "effect/PlatformError"; |
| ... | @@ -57,6 +56,16 @@ const layerHttpClient = (bytes: Uint8Array) => | ... | @@ -57,6 +56,16 @@ const layerHttpClient = (bytes: Uint8Array) => |
| 57 | ), | 56 | ), |
| 58 | ); | 57 | ); |
| 59 | | 58 | |
| | 59 | // Records each request and never responds, simulating a wedged endpoint. |
| | 60 | const layerStalledHttpClient = (requests: Array<unknown>) => |
| | 61 | Layer.succeed( |
| | 62 | HttpClient.HttpClient, |
| | 63 | HttpClient.make((request) => { |
| | 64 | requests.push(request); |
| | 65 | return Effect.never; |
| | 66 | }), |
| | 67 | ); |
| | 68 | |
| 60 | // Answers `cloudflared version` with the version recorded for that path, which | 69 | // Answers `cloudflared version` with the version recorded for that path, which |
| 61 | // defaults to the pinned release; tests change it to simulate other binaries. | 70 | // defaults to the pinned release; tests change it to simulate other binaries. |
| 62 | const layerSpawner = (commands: Array<string>, versions: Record<string, string> = {}) => | 71 | const layerSpawner = (commands: Array<string>, versions: Record<string, string> = {}) => |
| ... | @@ -767,4 +776,44 @@ describe("RelayClient", () => { | ... | @@ -767,4 +776,44 @@ describe("RelayClient", () => { |
| 767 | expect(renames.attempts).toBe(1); | 776 | expect(renames.attempts).toBe(1); |
| 768 | }).pipe(Effect.scoped, Effect.provide(layerInstallRuntime(platform))), | 777 | }).pipe(Effect.scoped, Effect.provide(layerInstallRuntime(platform))), |
| 769 | ); | 778 | ); |
| | 779 | |
| | 780 | it.effect("fails a stalled download after the download timeout", () => { |
| | 781 | const requests: Array<unknown> = []; |
| | 782 | return Effect.gen(function* () { |
| | 783 | const fileSystem = yield* FileSystem.FileSystem; |
| | 784 | const baseDir = yield* fileSystem.makeTempDirectoryScoped({ |
| | 785 | prefix: "t3-cloudflared-test-", |
| | 786 | }); |
| | 787 | const manager = yield* RelayClient.makeCloudflaredRelayClient({ |
| | 788 | baseDir, |
| | 789 | releaseAsset: { |
| | 790 | url: "https://example.test/cloudflared", |
| | 791 | sha256: "00".repeat(32), |
| | 792 | archive: "binary", |
| | 793 | }, |
| | 794 | }); |
| | 795 | |
| | 796 | const child = yield* Effect.forkChild(manager.install); |
| | 797 | // Spin until the wedged download is in flight, so the clock |
| | 798 | // adjustment below cannot run before the timeout is armed. |
| | 799 | while (requests.length === 0) { |
| | 800 | yield* Effect.yieldNow; |
| | 801 | } |
| | 802 | // The download timeout is 10 minutes; advance past it. |
| | 803 | yield* TestClock.adjust("11 minutes"); |
| | 804 | const error = yield* Fiber.join(child).pipe(Effect.flip); |
| | 805 | expect(error).toBeInstanceOf(RelayClient.RelayClientInstallError); |
| | 806 | expect(error.reason).toBe("download_failed"); |
| | 807 | }).pipe( |
| | 808 | Effect.scoped, |
| | 809 | Effect.provide( |
| | 810 | Layer.mergeAll( |
| | 811 | NodeServices.layer, |
| | 812 | layerStalledHttpClient(requests), |
| | 813 | layerSpawner([]), |
| | 814 | layerHostRuntime({ PATH: "" }), |
| | 815 | ), |
| | 816 | ), |
| | 817 | ); |
| | 818 | }); |
| 770 | }); | 819 | }); |