| ... | ... | @@ -236,6 +236,267 @@ for (const scenario of [ |
| 236 | 236 | ); |
| 237 | 237 | } |
| 238 | 238 | |
| 239 | it.effect("shares active read identity probes while keeping later reads and mutations fresh", () => |
| 240 | Effect.scoped( |
| 241 | Effect.gen(function* () { |
| 242 | const probes: string[] = []; |
| 243 | const reads: number[] = []; |
| 244 | const mutations: string[] = []; |
| 245 | let accountId = "123"; |
| 246 | const clientFor = (local: boolean) => |
| 247 | ({ |
| 248 | [local ? WS_METHODS.pullRequestsRoutingIdentity : WS_METHODS.pullRequestsRouting]: () => |
| 249 | Effect.yieldNow.pipe( |
| 250 | Effect.andThen( |
| 251 | Effect.sync(() => { |
| 252 | probes.push(local ? "local" : "origin"); |
| 253 | return { host: "github.com", provider: "github", viewer: "maria", accountId }; |
| 254 | }), |
| 255 | ), |
| 256 | ), |
| 257 | [WS_METHODS.pullRequestsSummary]: (input: { |
| 258 | number: number; |
| 259 | expectedAccountId: string; |
| 260 | }) => |
| 261 | Effect.sync(() => { |
| 262 | expect(local).toBe(true); |
| 263 | expect(input.expectedAccountId).toBe(accountId); |
| 264 | reads.push(input.number); |
| 265 | return null; |
| 266 | }), |
| 267 | [WS_METHODS.pullRequestsRunAction]: (input: { expectedAccountId: string }) => |
| 268 | Effect.sync(() => mutations.push(input.expectedAccountId)), |
| 269 | [WS_METHODS.pullRequestsInvalidate]: () => Effect.void, |
| 270 | }) as unknown as WsRpcProtocolClient; |
| 271 | const { environmentRegistry, supervisor } = yield* makeTestRuntime( |
| 272 | clientFor(false), |
| 273 | clientFor(true), |
| 274 | ); |
| 275 | const reference = { |
| 276 | projectId: ProjectId.make("project-1"), |
| 277 | repository: "acme/web", |
| 278 | number: 7, |
| 279 | }; |
| 280 | yield* Effect.gen(function* () { |
| 281 | yield* Effect.all( |
| 282 | Array.from({ length: 4 }, (_, index) => |
| 283 | createPullRequestRouter()(WS_METHODS.pullRequestsSummary, { |
| 284 | ...reference, |
| 285 | number: index + 1, |
| 286 | }), |
| 287 | ), |
| 288 | { concurrency: "unbounded" }, |
| 289 | ); |
| 290 | expect(probes).toEqual(["origin", "local"]); |
| 291 | expect(reads.toSorted()).toEqual([1, 2, 3, 4]); |
| 292 | accountId = "456"; |
| 293 | yield* createPullRequestRouter()(WS_METHODS.pullRequestsSummary, reference); |
| 294 | yield* createPullRequestRouter()(WS_METHODS.pullRequestsRunAction, { |
| 295 | ...reference, |
| 296 | action: "close", |
| 297 | }); |
| 298 | expect(probes).toEqual(["origin", "local", "origin", "local", "origin", "local"]); |
| 299 | expect(mutations).toEqual(["456"]); |
| 300 | }).pipe( |
| 301 | Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), |
| 302 | Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), |
| 303 | Effect.provideService(GitHubRoutingPermissions, trustedRouting), |
| 304 | ); |
| 305 | }), |
| 306 | ), |
| 307 | ); |
| 308 | |
| 309 | it.effect.each([ |
| 310 | { probe: "origin", cancelled: "first" }, |
| 311 | { probe: "origin", cancelled: "waiter" }, |
| 312 | { probe: "alternate", cancelled: "first" }, |
| 313 | { probe: "alternate", cancelled: "waiter" }, |
| 314 | ] as const)( |
| 315 | "preserves a shared $probe probe's other reader when the $cancelled cancels", |
| 316 | ({ probe, cancelled }) => |
| 317 | Effect.scoped( |
| 318 | Effect.gen(function* () { |
| 319 | const started = yield* Deferred.make<void>(); |
| 320 | const release = yield* Deferred.make<void>(); |
| 321 | let probes = 0; |
| 322 | const clientFor = (local: boolean) => |
| 323 | ({ |
| 324 | [local ? WS_METHODS.pullRequestsRoutingIdentity : WS_METHODS.pullRequestsRouting]: () => |
| 325 | Effect.gen(function* () { |
| 326 | if (local === (probe === "alternate")) { |
| 327 | probes++; |
| 328 | yield* Deferred.succeed(started, undefined); |
| 329 | yield* Deferred.await(release); |
| 330 | } |
| 331 | return { |
| 332 | host: "github.com", |
| 333 | provider: "github", |
| 334 | viewer: "maria", |
| 335 | accountId: "123", |
| 336 | }; |
| 337 | }), |
| 338 | [WS_METHODS.pullRequestsSummary]: () => Effect.succeed(null), |
| 339 | }) as unknown as WsRpcProtocolClient; |
| 340 | const { environmentRegistry, supervisor } = yield* makeTestRuntime( |
| 341 | clientFor(false), |
| 342 | clientFor(true), |
| 343 | ); |
| 344 | const read = () => |
| 345 | createPullRequestRouter()(WS_METHODS.pullRequestsSummary, { |
| 346 | projectId: ProjectId.make("project-1"), |
| 347 | repository: "acme/web", |
| 348 | number: 7, |
| 349 | }).pipe( |
| 350 | Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), |
| 351 | Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), |
| 352 | Effect.provideService(GitHubRoutingPermissions, trustedRouting), |
| 353 | ); |
| 354 | const first = yield* read().pipe(Effect.forkChild); |
| 355 | yield* Deferred.await(started); |
| 356 | const waiter = yield* read().pipe(Effect.forkChild); |
| 357 | yield* Effect.yieldNow; |
| 358 | yield* Fiber.interrupt(cancelled === "first" ? first : waiter); |
| 359 | yield* Deferred.succeed(release, undefined); |
| 360 | expect(yield* Fiber.join(cancelled === "first" ? waiter : first)).toBeNull(); |
| 361 | expect(probes).toBe(cancelled === "first" ? 2 : 1); |
| 362 | yield* read(); |
| 363 | expect(probes).toBe(cancelled === "first" ? 3 : 2); |
| 364 | }), |
| 365 | ), |
| 366 | ); |
| 367 | |
| 368 | it.effect.each(["revoked", "disabled"] as const)( |
| 369 | "rechecks routing after an alternate with a shared probe is %s", |
| 370 | (change) => |
| 371 | Effect.scoped( |
| 372 | Effect.gen(function* () { |
| 373 | const started = yield* Deferred.make<void>(); |
| 374 | const release = yield* Deferred.make<void>(); |
| 375 | const reads: string[] = []; |
| 376 | let trusted = true; |
| 377 | let probes = 0; |
| 378 | const clientFor = (local: boolean) => |
| 379 | ({ |
| 380 | [local ? WS_METHODS.pullRequestsRoutingIdentity : WS_METHODS.pullRequestsRouting]: () => |
| 381 | Effect.gen(function* () { |
| 382 | if (local) { |
| 383 | probes++; |
| 384 | yield* Deferred.succeed(started, undefined); |
| 385 | yield* Deferred.await(release); |
| 386 | } |
| 387 | return { |
| 388 | host: "github.com", |
| 389 | provider: "github", |
| 390 | viewer: "maria", |
| 391 | accountId: "123", |
| 392 | }; |
| 393 | }), |
| 394 | [WS_METHODS.pullRequestsSummary]: () => |
| 395 | Effect.sync(() => { |
| 396 | reads.push(local ? "local" : "origin"); |
| 397 | return null; |
| 398 | }), |
| 399 | }) as unknown as WsRpcProtocolClient; |
| 400 | const { environmentRegistry, supervisor } = yield* makeTestRuntime( |
| 401 | clientFor(false), |
| 402 | clientFor(true), |
| 403 | ); |
| 404 | const readers = yield* Effect.all( |
| 405 | Array.from({ length: 2 }, () => |
| 406 | createPullRequestRouter()(WS_METHODS.pullRequestsSummary, { |
| 407 | projectId: ProjectId.make("project-1"), |
| 408 | repository: "acme/web", |
| 409 | number: 7, |
| 410 | }), |
| 411 | ), |
| 412 | { concurrency: "unbounded" }, |
| 413 | ).pipe( |
| 414 | Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), |
| 415 | Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), |
| 416 | Effect.provideService(GitHubRoutingPermissions, { |
| 417 | ...trustedRouting, |
| 418 | get: () => Effect.sync(() => (trusted ? "read-write" : "off")), |
| 419 | }), |
| 420 | Effect.forkChild, |
| 421 | ); |
| 422 | yield* Deferred.await(started); |
| 423 | yield* Effect.yieldNow; |
| 424 | if (change === "revoked") trusted = false; |
| 425 | else |
| 426 | yield* SubscriptionRef.update( |
| 427 | environmentRegistry.entries, |
| 428 | (entries) => |
| 429 | new Map( |
| 430 | [...entries].map(([id, entry]) => [ |
| 431 | id, |
| 432 | id === TARGET.environmentId ? entry : { ...entry, enabled: false }, |
| 433 | ]), |
| 434 | ), |
| 435 | ); |
| 436 | yield* Deferred.succeed(release, undefined); |
| 437 | expect(yield* Fiber.join(readers)).toEqual([null, null]); |
| 438 | expect(probes).toBe(1); |
| 439 | expect(reads).toEqual(["origin", "origin"]); |
| 440 | }), |
| 441 | ), |
| 442 | ); |
| 443 | |
| 444 | it.effect.each(["origin", "alternate"] as const)( |
| 445 | "keeps pending metadata from an old %s connection separate from its replacement", |
| 446 | (reconnected) => |
| 447 | Effect.scoped( |
| 448 | Effect.gen(function* () { |
| 449 | const started = yield* Deferred.make<void>(); |
| 450 | const release = yield* Deferred.make<void>(); |
| 451 | const probes: string[] = []; |
| 452 | const clientFor = (local: boolean, replacement = false) => |
| 453 | ({ |
| 454 | [local ? WS_METHODS.pullRequestsRoutingIdentity : WS_METHODS.pullRequestsRouting]: () => |
| 455 | Effect.gen(function* () { |
| 456 | if (local === (reconnected === "alternate")) { |
| 457 | probes.push(replacement ? "new" : "old"); |
| 458 | if (!replacement) { |
| 459 | yield* Deferred.succeed(started, undefined); |
| 460 | yield* Deferred.await(release); |
| 461 | } |
| 462 | } |
| 463 | return { |
| 464 | host: "github.com", |
| 465 | provider: "github", |
| 466 | viewer: "maria", |
| 467 | accountId: "123", |
| 468 | }; |
| 469 | }), |
| 470 | [WS_METHODS.pullRequestsSummary]: () => Effect.succeed(null), |
| 471 | }) as unknown as WsRpcProtocolClient; |
| 472 | const { environmentRegistry, supervisor, localSupervisor } = yield* makeTestRuntime( |
| 473 | clientFor(false), |
| 474 | clientFor(true), |
| 475 | ); |
| 476 | const read = () => |
| 477 | createPullRequestRouter()(WS_METHODS.pullRequestsSummary, { |
| 478 | projectId: ProjectId.make("project-1"), |
| 479 | repository: "acme/web", |
| 480 | number: 7, |
| 481 | }).pipe( |
| 482 | Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, environmentRegistry), |
| 483 | Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), |
| 484 | Effect.provideService(GitHubRoutingPermissions, trustedRouting), |
| 485 | ); |
| 486 | const first = yield* read().pipe(Effect.forkChild); |
| 487 | yield* Deferred.await(started); |
| 488 | yield* SubscriptionRef.set( |
| 489 | (reconnected === "origin" ? supervisor : localSupervisor).session, |
| 490 | Option.some(session(clientFor(reconnected === "alternate", true))), |
| 491 | ); |
| 492 | expect(yield* read()).toBeNull(); |
| 493 | expect(probes).toEqual(["old", "new"]); |
| 494 | yield* Deferred.succeed(release, undefined); |
| 495 | expect(yield* Fiber.join(first)).toBeNull(); |
| 496 | }), |
| 497 | ), |
| 498 | ); |
| 499 | |
| 239 | 500 | it.effect.each(["github", "gitlab", "bitbucket", "azure-devops"] as const)( |
| 240 | 501 | "routes %s viewed marks to their storage environment", |
| 241 | 502 | (provider) => |
| ... | ... | @@ -417,7 +678,7 @@ const makeTestRuntime = Effect.fn("makeTestRuntime")(function* ( |
| 417 | 678 | const registry = yield* Effect.acquireRelease(Effect.sync(AtomRegistry.make), (registry) => |
| 418 | 679 | Effect.sync(() => registry.dispose()), |
| 419 | 680 | ); |
| 420 | | return { runtime, atoms, registry, environmentRegistry, supervisor }; |
| 681 | return { runtime, atoms, registry, environmentRegistry, supervisor, localSupervisor }; |
| 421 | 682 | }); |
| 422 | 683 | |
| 423 | 684 | it.effect.each(["default", "origin-off", "destination-off", "read-only"] as const)( |