| ... | @@ -236,6 +236,267 @@ for (const scenario of [ | ... | @@ -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 | it.effect.each(["github", "gitlab", "bitbucket", "azure-devops"] as const)( | 500 | it.effect.each(["github", "gitlab", "bitbucket", "azure-devops"] as const)( |
| 240 | "routes %s viewed marks to their storage environment", | 501 | "routes %s viewed marks to their storage environment", |
| 241 | (provider) => | 502 | (provider) => |
| ... | @@ -417,7 +678,7 @@ const makeTestRuntime = Effect.fn("makeTestRuntime")(function* ( | ... | @@ -417,7 +678,7 @@ const makeTestRuntime = Effect.fn("makeTestRuntime")(function* ( |
| 417 | const registry = yield* Effect.acquireRelease(Effect.sync(AtomRegistry.make), (registry) => | 678 | const registry = yield* Effect.acquireRelease(Effect.sync(AtomRegistry.make), (registry) => |
| 418 | Effect.sync(() => registry.dispose()), | 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 | it.effect.each(["default", "origin-off", "destination-off", "read-only"] as const)( | 684 | it.effect.each(["default", "origin-off", "destination-off", "read-only"] as const)( |