343 lines · 13.4 KB · TypeScript
RawDownload
import { assert, it } from "@effect/vitest";import type { RepositoryIdentity } from "@t3tools/contracts";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as PubSub from "effect/PubSub";import * as Ref from "effect/Ref"; import * as ProjectEnrichment from "./ProjectEnrichmentService.ts";import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts";import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts"; const identity = (workspaceRoot: string, version = 1): RepositoryIdentity => ({  canonicalKey: `example.test/v${version}${workspaceRoot}`,  locator: {    source: "git-remote",    remoteName: "origin",    remoteUrl: `https://example.test/v${version}${workspaceRoot}.git`,  },  rootPath: workspaceRoot,}); const waitForAvailable = Effect.fn("ProjectEnrichmentServiceTest.waitForAvailable")(function* (  service: ProjectEnrichment.ProjectEnrichmentService["Service"],  workspaceRoot: string,  predicate: (value: ProjectEnrichment.ProjectEnrichment) => boolean,) {  for (let attempt = 0; attempt < 100; attempt += 1) {    const available = yield* service.peek(workspaceRoot);    if (predicate(available)) return available;    yield* Effect.yieldNow;  }  return yield* Effect.die(`Project metadata for ${workspaceRoot} was not resolved in time.`);}); const makeLayer = (  metadataLayer: Layer.Layer<    | ProjectFaviconResolver.ProjectFaviconResolver    | RepositoryIdentityResolver.RepositoryIdentityResolver  >,  options: ProjectEnrichment.ProjectEnrichmentServiceOptions = {},) =>  Layer.effect(ProjectEnrichment.ProjectEnrichmentService, ProjectEnrichment.make(options)).pipe(    Layer.provide(metadataLayer),  ); it.effect("preserves either enrichment field when the other resolver fails", () =>  Effect.gen(function* () {    const metadataLayer = Layer.merge(      Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {        resolve: (workspaceRoot) =>          workspaceRoot === "/repo-fails"            ? Effect.die("repository resolver failed")            : Effect.succeed(identity(workspaceRoot)),      }),      Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {        resolvePath: (workspaceRoot) =>          workspaceRoot === "/favicon-fails"            ? Effect.fail(                new ProjectFaviconResolver.ProjectFaviconResolutionError({                  operation: "stat-candidate",                  workspaceRoot,                  cause: "favicon resolver failed",                }),              )            : Effect.succeed(`${workspaceRoot}/favicon.svg`),      }),    );     yield* Effect.gen(function* () {      const service = yield* ProjectEnrichment.ProjectEnrichmentService;      yield* service.request("/favicon-fails");      yield* service.request("/repo-fails");       const faviconFailure = yield* waitForAvailable(        service,        "/favicon-fails",        (value) => value.repositoryIdentity !== null,      );      assert.equal(        faviconFailure.repositoryIdentity?.canonicalKey,        "example.test/v1/favicon-fails",      );      assert.isNull(faviconFailure.faviconPath);       const repositoryFailure = yield* waitForAvailable(        service,        "/repo-fails",        (value) => value.faviconPath !== null,      );      assert.isNull(repositoryFailure.repositoryIdentity);      assert.equal(repositoryFailure.faviconPath, "/repo-fails/favicon.svg");    }).pipe(Effect.provide(makeLayer(metadataLayer)));  }),); it.effect("publishes repository completion while favicon enrichment is still pending", () =>  Effect.gen(function* () {    const releaseFavicon = yield* Deferred.make<void>();    const metadataLayer = Layer.merge(      Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {        resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)),      }),      Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {        resolvePath: (workspaceRoot) =>          Deferred.await(releaseFavicon).pipe(Effect.as(`${workspaceRoot}/favicon.svg`)),      }),    );     yield* Effect.gen(function* () {      const service = yield* ProjectEnrichment.ProjectEnrichmentService;      const changes = yield* service.subscribeChanges;      yield* service.request("/completed");       const change = yield* PubSub.take(changes);      assert.equal(change.workspaceRoot, "/completed");      assert.isTrue(change.repositoryIdentityResolved);      assert.equal(change.enrichment.repositoryIdentity?.canonicalKey, "example.test/v1/completed");      assert.isNull(change.enrichment.faviconPath);    }).pipe(Effect.provide(makeLayer(metadataLayer)));  }),); it.effect("keeps repository workers available when every favicon worker is hung", () =>  Effect.gen(function* () {    const faviconWorkersStarted = yield* Deferred.make<void>();    const faviconStarts = yield* Ref.make(0);    const metadataLayer = Layer.merge(      Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {        resolve: (workspaceRoot) => Effect.succeed(identity(workspaceRoot)),      }),      Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {        resolvePath: () =>          Effect.gen(function* () {            const started = yield* Ref.updateAndGet(faviconStarts, (count) => count + 1);            if (started === 2) {              yield* Deferred.succeed(faviconWorkersStarted, undefined);            }            return yield* Effect.never;          }),      }),    );     yield* Effect.gen(function* () {      const service = yield* ProjectEnrichment.ProjectEnrichmentService;      yield* service.request("/favicon-hung-a");      yield* service.request("/favicon-hung-b");      yield* Deferred.await(faviconWorkersStarted);       yield* service.request("/repository-later");      const available = yield* waitForAvailable(        service,        "/repository-later",        (value) => value.repositoryIdentity !== null,      );       assert.equal(available.repositoryIdentity?.canonicalKey, "example.test/v1/repository-later");      assert.equal(yield* Ref.get(faviconStarts), 2);    }).pipe(      Effect.provide(        makeLayer(metadataLayer, {          cacheCapacity: 8,          maxPending: 4,          concurrency: 2,        }),      ),    );  }),); it.effect("getAvailable returns immediately while repository identity is still unresolved", () =>  Effect.gen(function* () {    const repositoryStarted = yield* Deferred.make<void>();    const releaseRepository = yield* Deferred.make<void>();    const metadataLayer = Layer.merge(      Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {        resolve: (workspaceRoot) =>          Deferred.succeed(repositoryStarted, undefined).pipe(            Effect.andThen(Deferred.await(releaseRepository)),            Effect.as(identity(workspaceRoot)),          ),      }),      Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {        resolvePath: (workspaceRoot) => Effect.succeed(`${workspaceRoot}/favicon.svg`),      }),    );     yield* Effect.gen(function* () {      const service = yield* ProjectEnrichment.ProjectEnrichmentService;       // Non-blocking path must not wait on hung git/identity resolution.      const immediate = yield* service.getAvailable("/pending-identity");      assert.isNull(immediate.repositoryIdentity);      assert.isNull(immediate.faviconPath);      assert.isFalse(immediate.repositoryIdentityResolved);       yield* Deferred.await(repositoryStarted);       // Background lane still resolves after the snapshot path has returned.      yield* Deferred.succeed(releaseRepository, undefined);      const resolved = yield* waitForAvailable(        service,        "/pending-identity",        (value) => value.repositoryIdentity !== null,      );      assert.equal(resolved.repositoryIdentity?.canonicalKey, "example.test/v1/pending-identity");      assert.isTrue(resolved.repositoryIdentityResolved);    }).pipe(Effect.provide(makeLayer(metadataLayer)));  }),); it.effect(  "reports successful cached null as resolved while cold and failed lookups stay unresolved",  () =>    Effect.gen(function* () {      const repositoryStarted = yield* Deferred.make<void>();      const releaseRepository = yield* Deferred.make<void>();      const metadataLayer = Layer.merge(        Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {          resolve: (workspaceRoot) => {            if (workspaceRoot === "/no-remote") {              return Effect.succeed(null);            }            if (workspaceRoot === "/fails") {              return Effect.die("repository resolver failed");            }            return Deferred.succeed(repositoryStarted, undefined).pipe(              Effect.andThen(Deferred.await(releaseRepository)),              Effect.as(identity(workspaceRoot)),            );          },        }),        Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {          resolvePath: () => Effect.succeed(null),        }),      );       yield* Effect.gen(function* () {        const service = yield* ProjectEnrichment.ProjectEnrichmentService;        const changes = yield* service.subscribeChanges;         const cold = yield* service.peek("/never-requested");        assert.isNull(cold.repositoryIdentity);        assert.isFalse(cold.repositoryIdentityResolved);         const inFlight = yield* service.getAvailable("/in-flight");        assert.isNull(inFlight.repositoryIdentity);        assert.isFalse(inFlight.repositoryIdentityResolved);        yield* Deferred.await(repositoryStarted);        assert.isFalse((yield* service.peek("/in-flight")).repositoryIdentityResolved);         yield* service.request("/no-remote");        const nullChange = yield* PubSub.take(changes);        assert.equal(nullChange.workspaceRoot, "/no-remote");        assert.isTrue(nullChange.repositoryIdentityResolved);        assert.isNull(nullChange.enrichment.repositoryIdentity);        assert.isTrue(nullChange.enrichment.repositoryIdentityResolved);         // Warm success null stays resolved and does not republish.        const warmNull = yield* service.getAvailable("/no-remote");        assert.isNull(warmNull.repositoryIdentity);        assert.isTrue(warmNull.repositoryIdentityResolved);         yield* service.request("/fails");        const failChange = yield* PubSub.take(changes);        assert.equal(failChange.workspaceRoot, "/fails");        assert.isFalse(failChange.repositoryIdentityResolved);        const failed = yield* service.peek("/fails");        assert.isNull(failed.repositoryIdentity);        assert.isFalse(failed.repositoryIdentityResolved);      }).pipe(Effect.provide(makeLayer(metadataLayer)));    }),); it.effect("deduplicates requests, bounds pending work, and reloads invalidated roots", () =>  Effect.gen(function* () {    const firstStarted = yield* Deferred.make<void>();    const releaseFirst = yield* Deferred.make<void>();    const repositoryCalls = yield* Ref.make<ReadonlyArray<string>>([]);    const version = yield* Ref.make(1);    const metadataLayer = Layer.merge(      Layer.succeed(RepositoryIdentityResolver.RepositoryIdentityResolver, {        resolve: (workspaceRoot) =>          Effect.gen(function* () {            yield* Ref.update(repositoryCalls, (calls) => [...calls, workspaceRoot]);            if (workspaceRoot === "/first" && (yield* Ref.get(version)) === 1) {              yield* Deferred.succeed(firstStarted, undefined);              yield* Deferred.await(releaseFirst);            }            return identity(workspaceRoot, yield* Ref.get(version));          }),      }),      Layer.succeed(ProjectFaviconResolver.ProjectFaviconResolver, {        resolvePath: (workspaceRoot) => Effect.succeed(`${workspaceRoot}/favicon.svg`),      }),    );     yield* Effect.gen(function* () {      const service = yield* ProjectEnrichment.ProjectEnrichmentService;      yield* Effect.forEach(Array.from({ length: 20 }), () => service.request("/first"), {        concurrency: "unbounded",        discard: true,      });      yield* Deferred.await(firstStarted);      yield* service.request("/second");      yield* service.request("/dropped");      assert.deepEqual(yield* Ref.get(repositoryCalls), ["/first"]);       yield* Deferred.succeed(releaseFirst, undefined);      yield* waitForAvailable(service, "/second", (value) => value.repositoryIdentity !== null);      assert.deepEqual(yield* Ref.get(repositoryCalls), ["/first", "/second"]);       yield* service.request("/dropped");      yield* waitForAvailable(service, "/dropped", (value) => value.repositoryIdentity !== null);      assert.deepEqual(yield* Ref.get(repositoryCalls), ["/first", "/second", "/dropped"]);       yield* Ref.set(version, 2);      yield* service.invalidate(["/first"]);      const invalidated = yield* service.getAvailable("/first");      assert.isNull(invalidated.repositoryIdentity);      const refreshed = yield* waitForAvailable(        service,        "/first",        (value) => value.repositoryIdentity?.canonicalKey === "example.test/v2/first",      );      assert.equal(refreshed.repositoryIdentity?.canonicalKey, "example.test/v2/first");      assert.deepEqual(yield* Ref.get(repositoryCalls), [        "/first",        "/second",        "/dropped",        "/first",      ]);    }).pipe(      Effect.provide(        makeLayer(metadataLayer, {          cacheCapacity: 8,          maxPending: 2,          concurrency: 1,        }),      ),    );  }),);