Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions apps/server/src/review/ReviewService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
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 PlatformError from "effect/PlatformError";

import type { OrchestrationProject } from "@t3tools/contracts";

import { ServerConfig } from "../config.ts";
import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";
import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
import * as ReviewService from "./ReviewService.ts";
Expand All @@ -14,6 +19,8 @@ function makeLayer(input: {
readonly workspaceRoot: string;
readonly baseDir: string;
readonly detectCalls?: Array<{ readonly cwd: string }>;
readonly registeredProjectRoots?: ReadonlySet<string>;
readonly projectQueryCalls?: Array<string>;
}) {
return ReviewService.layer.pipe(
Layer.provide(
Expand All @@ -28,6 +35,17 @@ function makeLayer(input: {
}),
),
Layer.provide(Layer.mock(GitVcsDriver.GitVcsDriver)({})),
Layer.provide(
Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({
getActiveProjectByWorkspaceRoot: (workspaceRoot) =>
Effect.sync(() => {
input.projectQueryCalls?.push(workspaceRoot);
return input.registeredProjectRoots?.has(workspaceRoot)
? Option.some({} as OrchestrationProject)
: Option.none();
}),
}),
),
Layer.provide(ServerConfig.layerTest(input.workspaceRoot, input.baseDir)),
Layer.provideMerge(NodeServices.layer),
);
Expand Down Expand Up @@ -75,6 +93,108 @@ describe("ReviewService", () => {
}).pipe(Effect.provide(NodeServices.layer)),
);

it.effect("allows diff preview cwd inside an active registered project", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
const registeredRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-project-" });
const nestedCwd = path.join(registeredRoot, "packages", "app");
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
yield* fs.makeDirectory(nestedCwd, { recursive: true });
const detectCalls: Array<{ readonly cwd: string }> = [];
const projectQueryCalls: Array<string> = [];

const result = yield* Effect.gen(function* () {
const review = yield* ReviewService.ReviewService;
return yield* review.getDiffPreview({ cwd: nestedCwd });
}).pipe(
Effect.provide(
makeLayer({
workspaceRoot,
baseDir,
detectCalls,
registeredProjectRoots: new Set([registeredRoot]),
projectQueryCalls,
}),
),
);

assert.strictEqual(result.cwd, nestedCwd);
assert.deepStrictEqual(detectCalls, [{ cwd: nestedCwd }]);
assert.deepStrictEqual(projectQueryCalls.slice(0, 3), [
nestedCwd,
path.dirname(nestedCwd),
registeredRoot,
]);
}).pipe(Effect.provide(NodeServices.layer)),
);

it.effect("rejects a registered-project symlink that escapes its canonical root", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
const registeredRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-project-" });
const outsideRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-outside-" });
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
const escapedCwd = path.join(registeredRoot, "linked-outside");
yield* fs.symlink(outsideRoot, escapedCwd);
const detectCalls: Array<{ readonly cwd: string }> = [];

const error = yield* Effect.gen(function* () {
const review = yield* ReviewService.ReviewService;
return yield* review.getDiffPreview({ cwd: escapedCwd }).pipe(Effect.flip);
}).pipe(
Effect.provide(
makeLayer({
workspaceRoot,
baseDir,
detectCalls,
registeredProjectRoots: new Set([registeredRoot]),
}),
),
);

assert.strictEqual(error._tag, "VcsRepositoryDetectionError");
assert.strictEqual(error.operation, "ReviewService.getDiffPreview");
assert.deepStrictEqual(detectCalls, []);
}).pipe(Effect.provide(NodeServices.layer)),
);

it.effect("allows a cwd inside a registered project whose root is a symlink", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const workspaceRoot = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-workspace-" });
const projectParent = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-parent-" });
const canonicalProject = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-project-" });
const registeredRoot = path.join(projectParent, "linked-project");
const nestedCwd = path.join(registeredRoot, "packages", "app");
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-review-base-" });
yield* fs.makeDirectory(path.join(canonicalProject, "packages", "app"), { recursive: true });
yield* fs.symlink(canonicalProject, registeredRoot);
const detectCalls: Array<{ readonly cwd: string }> = [];

const result = yield* Effect.gen(function* () {
const review = yield* ReviewService.ReviewService;
return yield* review.getDiffPreview({ cwd: nestedCwd });
}).pipe(
Effect.provide(
makeLayer({
workspaceRoot,
baseDir,
detectCalls,
registeredProjectRoots: new Set([registeredRoot]),
}),
),
);

assert.strictEqual(result.cwd, nestedCwd);
assert.deepStrictEqual(detectCalls, [{ cwd: nestedCwd }]);
}).pipe(Effect.provide(NodeServices.layer)),
);

it.effect("rejects option-like base refs before invoking a VCS driver", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
Expand Down
36 changes: 36 additions & 0 deletions apps/server/src/review/ReviewService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
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 {
Expand All @@ -14,6 +15,7 @@ import {
} from "@t3tools/contracts";

import * as ServerConfig from "../config.ts";
import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";
import * as GitVcsDriver from "../vcs/GitVcsDriver.ts";
import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";

Expand All @@ -32,6 +34,7 @@ export const make = Effect.gen(function* () {
const path = yield* Path.Path;
const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry;
const git = yield* GitVcsDriver.GitVcsDriver;
const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;

const canonicalizePath = (value: string) => {
const resolvedPath = path.resolve(value);
Expand All @@ -57,9 +60,38 @@ export const make = Effect.gen(function* () {
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
};

const hasActiveProjectAncestor = Effect.fn("ReviewService.hasActiveProjectAncestor")(function* (
resolvedCandidate: string,
canonicalCandidate: string,
) {
let current = resolvedCandidate;
while (true) {
const project = yield* projectionSnapshotQuery.getActiveProjectByWorkspaceRoot(current).pipe(
Effect.mapError(
(cause) =>
new VcsRepositoryDetectionError({
operation: "ReviewService.assertWorkspaceBoundCwd.queryActiveProject",
cwd: resolvedCandidate,
detail: "Failed to validate the review path against active projects.",
cause,
}),
),
);
if (Option.isSome(project)) {
const canonicalProjectRoot = yield* canonicalizePath(current);
return isWithinRoot(canonicalCandidate, canonicalProjectRoot);
}

const parent = path.dirname(current);
if (parent === current) return false;
current = parent;
}
});

const assertWorkspaceBoundCwd = Effect.fn("ReviewService.assertWorkspaceBoundCwd")(function* (
cwd: string,
) {
const resolvedCandidate = path.resolve(cwd);
const [candidate, workspaceRoot, worktreesRoot] = yield* Effect.all([
canonicalizePath(cwd),
canonicalizePath(config.cwd),
Expand All @@ -70,6 +102,10 @@ export const make = Effect.gen(function* () {
return;
}

if (yield* hasActiveProjectAncestor(resolvedCandidate, candidate)) {
return;
Comment thread
logancsack marked this conversation as resolved.
Outdated
}

return yield* new VcsRepositoryDetectionError({
operation: "ReviewService.getDiffPreview",
cwd,
Expand Down
14 changes: 1 addition & 13 deletions apps/web/src/components/settings/GrokReviewSettings.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import { describe, expect, it } from "vite-plus/test";

import { aldoReviewToggleDisabled, normalizeGrokReviewRepository } from "./GrokReviewSettings";

describe("automatic Grok review repository input", () => {
it("normalizes GitHub owner/repository names", () => {
expect(normalizeGrokReviewRepository(" Aldo/Platform ")).toBe("aldo/platform");
});

it("rejects path traversal and incomplete names", () => {
expect(normalizeGrokReviewRepository("../platform")).toBeNull();
expect(normalizeGrokReviewRepository("aldo")).toBeNull();
expect(normalizeGrokReviewRepository("aldo/platform/extra")).toBeNull();
});
});
import { aldoReviewToggleDisabled } from "./GrokReviewSettings";

describe("aldoReviewToggleDisabled", () => {
it("allows an enabled setting to be disabled after the app disconnects", () => {
Expand Down
Loading
Loading