420 lines · 14.7 KB · TypeScript
RawDownload
import { assert, it, describe } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";import * as Deferred from "effect/Deferred";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream";import type {  VcsStatusLocalResult,  VcsStatusRemoteResult,  VcsStatusResult,  VcsStatusStreamEvent,} from "@t3tools/contracts"; import * as VcsStatusBroadcaster from "./VcsStatusBroadcaster.ts";import * as GitWorkflowService from "../git/GitWorkflowService.ts"; const baseLocalStatus: VcsStatusLocalResult = {  isRepo: true,  sourceControlProvider: {    kind: "github",    name: "GitHub",    baseUrl: "https://github.com",  },  hasPrimaryRemote: true,  isDefaultRef: false,  refName: "feature/status-broadcast",  hasWorkingTreeChanges: false,  workingTree: { files: [], insertions: 0, deletions: 0 },}; const baseRemoteStatus: VcsStatusRemoteResult = {  hasUpstream: true,  aheadCount: 0,  behindCount: 0,  pr: null,}; const baseStatus: VcsStatusResult = {  ...baseLocalStatus,  ...baseRemoteStatus,}; function makeTestLayer(state: {  currentLocalStatus: VcsStatusLocalResult;  currentRemoteStatus: VcsStatusRemoteResult | null;  localStatusCalls: number;  remoteStatusCalls: number;  localInvalidationCalls: number;  remoteInvalidationCalls: number;}) {  return VcsStatusBroadcaster.layer.pipe(    Layer.provideMerge(NodeServices.layer),    Layer.provide(      Layer.mock(GitWorkflowService.GitWorkflowService)({        localStatus: () =>          Effect.sync(() => {            state.localStatusCalls += 1;            return state.currentLocalStatus;          }),        remoteStatus: () =>          Effect.sync(() => {            state.remoteStatusCalls += 1;            return state.currentRemoteStatus;          }),        invalidateLocalStatus: () =>          Effect.sync(() => {            state.localInvalidationCalls += 1;          }),        invalidateRemoteStatus: () =>          Effect.sync(() => {            state.remoteInvalidationCalls += 1;          }),      }),    ),  );} describe("VcsStatusBroadcaster", () => {  it.effect("reuses the cached VCS status across repeated reads", () => {    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };     return Effect.gen(function* () {      const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;       const first = yield* broadcaster.getStatus({ cwd: "/repo" });      const second = yield* broadcaster.getStatus({ cwd: "/repo" });       assert.deepStrictEqual(first, baseStatus);      assert.deepStrictEqual(second, baseStatus);      assert.equal(state.localStatusCalls, 1);      assert.equal(state.remoteStatusCalls, 1);      assert.equal(state.localInvalidationCalls, 0);      assert.equal(state.remoteInvalidationCalls, 0);    }).pipe(Effect.provide(makeTestLayer(state)));  });   it.effect("refreshes the cached snapshot after explicit invalidation", () => {    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };     return Effect.gen(function* () {      const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;      const initial = yield* broadcaster.getStatus({ cwd: "/repo" });       state.currentLocalStatus = {        ...baseLocalStatus,        refName: "feature/updated-status",      };      state.currentRemoteStatus = {        ...baseRemoteStatus,        aheadCount: 2,      };      const refreshed = yield* broadcaster.refreshStatus("/repo");      const cached = yield* broadcaster.getStatus({ cwd: "/repo" });       assert.deepStrictEqual(initial, baseStatus);      assert.deepStrictEqual(refreshed, {        ...state.currentLocalStatus,        ...state.currentRemoteStatus,      });      assert.deepStrictEqual(cached, {        ...state.currentLocalStatus,        ...state.currentRemoteStatus,      });      assert.equal(state.localStatusCalls, 2);      assert.equal(state.remoteStatusCalls, 2);      assert.equal(state.localInvalidationCalls, 1);      assert.equal(state.remoteInvalidationCalls, 1);    }).pipe(Effect.provide(makeTestLayer(state)));  });   it.effect("refreshes only the cached local snapshot when requested", () => {    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };     return Effect.gen(function* () {      const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;      const initial = yield* broadcaster.getStatus({ cwd: "/repo" });       state.currentLocalStatus = {        ...baseLocalStatus,        refName: "feature/local-only-refresh",        hasWorkingTreeChanges: true,      };       const refreshedLocal = yield* broadcaster.refreshLocalStatus("/repo");      const cached = yield* broadcaster.getStatus({ cwd: "/repo" });       assert.deepStrictEqual(initial, baseStatus);      assert.deepStrictEqual(refreshedLocal, state.currentLocalStatus);      assert.deepStrictEqual(cached, {        ...state.currentLocalStatus,        ...baseRemoteStatus,      });      assert.equal(state.localStatusCalls, 2);      assert.equal(state.remoteStatusCalls, 1);      assert.equal(state.localInvalidationCalls, 1);      assert.equal(state.remoteInvalidationCalls, 0);    }).pipe(Effect.provide(makeTestLayer(state)));  });   it.effect("normalizes symlinked CWDs before cache lookup and workflow calls", () => {    const seenCwds: string[] = [];    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };    const testLayer = VcsStatusBroadcaster.layer.pipe(      Layer.provideMerge(NodeServices.layer),      Layer.provide(        Layer.mock(GitWorkflowService.GitWorkflowService)({          localStatus: (input) =>            Effect.sync(() => {              seenCwds.push(input.cwd);              state.localStatusCalls += 1;              return state.currentLocalStatus;            }),          remoteStatus: (input) =>            Effect.sync(() => {              seenCwds.push(input.cwd);              state.remoteStatusCalls += 1;              return state.currentRemoteStatus;            }),          invalidateLocalStatus: () =>            Effect.sync(() => {              state.localInvalidationCalls += 1;            }),          invalidateRemoteStatus: () =>            Effect.sync(() => {              state.remoteInvalidationCalls += 1;            }),        } satisfies Partial<GitWorkflowService.GitWorkflowServiceShape>),      ),    );     return Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const realDir = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-vcs-status-real-",      });      const linkParent = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-vcs-status-link-",      });      const linkDir = path.join(linkParent, "repo-link");      yield* fileSystem.symlink(realDir, linkDir);      const realPath = yield* fileSystem.realPath(realDir);       const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;      yield* broadcaster.getStatus({ cwd: linkDir });      yield* broadcaster.getStatus({ cwd: realDir });       assert.deepStrictEqual(seenCwds, [realPath, realPath]);      assert.equal(state.localStatusCalls, 1);      assert.equal(state.remoteStatusCalls, 1);    }).pipe(Effect.provide(testLayer));  });   it.effect("streams a local snapshot first and remote updates later", () => {    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };     return Effect.gen(function* () {      const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;      const snapshotDeferred = yield* Deferred.make<VcsStatusStreamEvent>();      const remoteUpdatedDeferred = yield* Deferred.make<VcsStatusStreamEvent>();      yield* Stream.runForEach(broadcaster.streamStatus({ cwd: "/repo" }), (event) => {        if (event._tag === "snapshot") {          return Deferred.succeed(snapshotDeferred, event).pipe(Effect.ignore);        }        if (event._tag === "remoteUpdated") {          return Deferred.succeed(remoteUpdatedDeferred, event).pipe(Effect.ignore);        }        return Effect.void;      }).pipe(Effect.forkScoped);       const snapshot = yield* Deferred.await(snapshotDeferred);      yield* broadcaster.refreshStatus("/repo");      const remoteUpdated = yield* Deferred.await(remoteUpdatedDeferred);       assert.deepStrictEqual(snapshot, {        _tag: "snapshot",        local: baseLocalStatus,        remote: null,      } satisfies VcsStatusStreamEvent);      assert.deepStrictEqual(remoteUpdated, {        _tag: "remoteUpdated",        remote: baseRemoteStatus,      } satisfies VcsStatusStreamEvent);    }).pipe(Effect.provide(makeTestLayer(state)));  });   it.effect("does not start automatic remote refreshes when disabled", () => {    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };     return Effect.gen(function* () {      const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;      const snapshot = yield* Stream.runHead(        broadcaster.streamStatus(          { cwd: "/repo" },          { automaticRemoteRefreshInterval: Effect.succeed(Duration.zero) },        ),      );       assert.isTrue(Option.isSome(snapshot));      assert.equal(state.remoteStatusCalls, 0);      assert.equal(state.remoteInvalidationCalls, 0);    }).pipe(Effect.provide(makeTestLayer(state)));  });   it("backs off remote refresh failures exponentially and honors larger configured intervals", () => {    assert.equal(      Duration.toMillis(VcsStatusBroadcaster.remoteRefreshFailureDelay(1, Duration.seconds(1))),      30_000,    );    assert.equal(      Duration.toMillis(VcsStatusBroadcaster.remoteRefreshFailureDelay(2, Duration.seconds(1))),      60_000,    );    assert.equal(      Duration.toMillis(VcsStatusBroadcaster.remoteRefreshFailureDelay(3, Duration.seconds(1))),      120_000,    );    assert.equal(      Duration.toMillis(VcsStatusBroadcaster.remoteRefreshFailureDelay(1, Duration.minutes(5))),      300_000,    );    assert.equal(      Duration.toMillis(VcsStatusBroadcaster.remoteRefreshFailureDelay(20, Duration.seconds(1))),      900_000,    );  });   it.effect("stops the remote poller after the last stream subscriber disconnects", () => {    const state = {      currentLocalStatus: baseLocalStatus,      currentRemoteStatus: baseRemoteStatus,      localStatusCalls: 0,      remoteStatusCalls: 0,      localInvalidationCalls: 0,      remoteInvalidationCalls: 0,    };    let remoteInterruptedDeferred: Deferred.Deferred<void, never> | null = null;    let remoteStartedDeferred: Deferred.Deferred<void, never> | null = null;    const testLayer = VcsStatusBroadcaster.layer.pipe(      Layer.provideMerge(NodeServices.layer),      Layer.provide(        Layer.mock(GitWorkflowService.GitWorkflowService)({          localStatus: () =>            Effect.sync(() => {              state.localStatusCalls += 1;              return state.currentLocalStatus;            }),          remoteStatus: () =>            Effect.sync(() => {              state.remoteStatusCalls += 1;            }).pipe(              Effect.andThen(                remoteStartedDeferred                  ? Deferred.succeed(remoteStartedDeferred, undefined).pipe(Effect.ignore)                  : Effect.void,              ),              Effect.andThen(Effect.never as Effect.Effect<VcsStatusRemoteResult | null, never>),              Effect.onInterrupt(() =>                remoteInterruptedDeferred                  ? Deferred.succeed(remoteInterruptedDeferred, undefined).pipe(Effect.ignore)                  : Effect.void,              ),            ),          invalidateLocalStatus: () =>            Effect.sync(() => {              state.localInvalidationCalls += 1;            }),          invalidateRemoteStatus: () =>            Effect.sync(() => {              state.remoteInvalidationCalls += 1;            }),        } satisfies Partial<GitWorkflowService.GitWorkflowServiceShape>),      ),    );     return Effect.gen(function* () {      const remoteInterrupted = yield* Deferred.make<void>();      const remoteStarted = yield* Deferred.make<void>();      remoteInterruptedDeferred = remoteInterrupted;      remoteStartedDeferred = remoteStarted;       const broadcaster = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;      const firstSnapshot = yield* Deferred.make<VcsStatusStreamEvent>();      const secondSnapshot = yield* Deferred.make<VcsStatusStreamEvent>();      const firstScope = yield* Scope.make();      const secondScope = yield* Scope.make();      yield* Stream.runForEach(broadcaster.streamStatus({ cwd: "/repo" }), (event) =>        event._tag === "snapshot"          ? Deferred.succeed(firstSnapshot, event).pipe(Effect.ignore)          : Effect.void,      ).pipe(Effect.forkIn(firstScope));      yield* Stream.runForEach(broadcaster.streamStatus({ cwd: "/repo" }), (event) =>        event._tag === "snapshot"          ? Deferred.succeed(secondSnapshot, event).pipe(Effect.ignore)          : Effect.void,      ).pipe(Effect.forkIn(secondScope));       yield* Deferred.await(firstSnapshot);      yield* Deferred.await(secondSnapshot);      yield* Deferred.await(remoteStarted);       assert.equal(state.remoteStatusCalls, 1);       yield* Scope.close(firstScope, Exit.void);      assert.isTrue(Option.isNone(yield* Deferred.poll(remoteInterrupted)));       yield* Scope.close(secondScope, Exit.void).pipe(Effect.forkScoped);      yield* Deferred.await(remoteInterrupted);      assert.isTrue(Option.isSome(yield* Deferred.poll(remoteInterrupted)));    }).pipe(Effect.provide(testLayer));  });});