Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
155 changes: 120 additions & 35 deletions apps/server/src/git/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { expect } from "vite-plus/test";
import type {
GitActionProgressEvent,
GitPreparePullRequestThreadInput,
ModelSelection,
ThreadId,
} from "@t3tools/contracts";

Expand Down Expand Up @@ -62,38 +61,7 @@ function fakeGhOutput(stdout: string): VcsProcess.VcsProcessOutput {
};
}

interface FakeGitTextGeneration {
generateCommitMessage: (input: {
cwd: string;
branch: string | null;
stagedSummary: string;
stagedPatch: string;
includeBranch?: boolean;
modelSelection: ModelSelection;
}) => Effect.Effect<
{ subject: string; body: string; branch?: string | undefined },
TextGenerationError
>;
generatePrContent: (input: {
cwd: string;
baseBranch: string;
headBranch: string;
commitSummary: string;
diffSummary: string;
diffPatch: string;
modelSelection: ModelSelection;
}) => Effect.Effect<{ title: string; body: string }, TextGenerationError>;
generateBranchName: (input: {
cwd: string;
message: string;
modelSelection: ModelSelection;
}) => Effect.Effect<{ branch: string }, TextGenerationError>;
generateThreadTitle: (input: {
cwd: string;
message: string;
modelSelection: ModelSelection;
}) => Effect.Effect<{ title: string }, TextGenerationError>;
}
type FakeGitTextGeneration = TextGeneration.TextGeneration["Service"];

type FakePullRequest = NonNullable<FakeGhScenario["pullRequest"]>;

Expand Down Expand Up @@ -637,6 +605,7 @@ function preparePullRequestThread(
function makeManager(input?: {
ghScenario?: FakeGhScenario;
textGeneration?: Partial<FakeGitTextGeneration>;
serverSettings?: Parameters<typeof ServerSettings.layerTest>[0];
setupScriptRunner?: ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"];
}) {
const { service: gitHubCli, ghCalls } = createGitHubCliWithFakeGh(input?.ghScenario);
Expand All @@ -645,7 +614,7 @@ function makeManager(input?: {
prefix: "t3-git-manager-test-",
});

const serverSettingsLayer = ServerSettings.ServerSettingsService.layerTest();
const serverSettingsLayer = ServerSettings.ServerSettingsService.layerTest(input?.serverSettings);

const vcsDriverLayer = GitVcsDriver.layer.pipe(
Layer.provideMerge(VcsProcess.layer),
Expand Down Expand Up @@ -1341,8 +1310,22 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nworld\n");
let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined;

const { manager } = yield* makeManager();
const { manager } = yield* makeManager({
serverSettings: {
textGenerationStyle: {
mode: "custom" as const,
customInstructions: "Use a direct tone.",
},
},
textGeneration: {
generateCommitMessage: (input) => {
generatedPolicy = input.policy;
return Effect.succeed({ subject: "Implement stacked git actions", body: "" });
},
},
});
const result = yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
Expand All @@ -1352,6 +1335,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
expect(result.commit.status).toBe("created");
expect(result.push.status).toBe("skipped_not_requested");
expect(result.pr.status).toBe("skipped_not_requested");
expect(generatedPolicy).toMatchObject({ commitInstructions: "Use a direct tone." });
expect(result.toast).toMatchObject({
description: "Implement stacked git actions",
cta: {
Expand All @@ -1371,6 +1355,78 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
}),
);

it.effect("preserves custom style when instructions are empty", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\nworld\n");
let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined;

const { manager } = yield* makeManager({
serverSettings: {
textGenerationStyle: {
mode: "custom" as const,
customInstructions: "",
},
},
textGeneration: {
generateCommitMessage: (input) => {
generatedPolicy = input.policy;
return Effect.succeed({ subject: "Preserve custom style", body: "" });
},
},
});
yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
});

expect(generatedPolicy).toEqual({
kind: "custom",
inferRepositoryConventions: false,
});
}),
);

it.effect("preserves repository conventions style when recent history is empty", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* runGit(repoDir, ["init", "--initial-branch=main"]);
yield* runGit(repoDir, ["config", "user.email", "test@example.com"]);
yield* runGit(repoDir, ["config", "user.name", "Test User"]);
NodeFS.writeFileSync(NodePath.join(repoDir, "README.md"), "hello\n");
yield* runGit(repoDir, ["add", "README.md"]);
let generatedPolicy: TextGeneration.CommitMessageGenerationInput["policy"] = undefined;

const { manager } = yield* makeManager({
serverSettings: {
textGenerationStyle: {
mode: "repo_conventions" as const,
},
},
textGeneration: {
generateCommitMessage: (input) => {
generatedPolicy = input.policy;
return Effect.succeed({ subject: "Create initial commit", body: "" });
},
},
});
yield* runStackedAction(manager, {
cwd: repoDir,
action: "commit",
});

expect(generatedPolicy).toEqual({
kind: "repo_conventions",
commitInstructions:
"Follow the repository's established commit message style when examples are available.",
changeRequestInstructions:
"Follow the repository's established change request title and body style when examples are available.",
inferRepositoryConventions: true,
});
}),
);

it.effect("uses custom commit message when provided", () =>
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
Expand Down Expand Up @@ -2191,6 +2247,13 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
Effect.gen(function* () {
const repoDir = yield* makeTempDir("t3code-git-manager-");
yield* initRepo(repoDir);
NodeFS.mkdirSync(NodePath.join(repoDir, ".github"));
NodeFS.writeFileSync(
NodePath.join(repoDir, ".github", "pull_request_template.md"),
"## What changed?\n\n## Verification",
);
yield* runGit(repoDir, ["add", ".github/pull_request_template.md"]);
yield* runGit(repoDir, ["commit", "-m", "Add pull request template"]);
yield* runGit(repoDir, ["checkout", "-b", "feature-create-pr"]);
const remoteDir = yield* createBareRemote();
yield* runGit(repoDir, ["remote", "add", "origin", remoteDir]);
Expand All @@ -2199,8 +2262,26 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
yield* runGit(repoDir, ["commit", "-m", "Feature commit"]);
yield* runGit(repoDir, ["push", "-u", "origin", "feature-create-pr"]);
yield* runGit(repoDir, ["config", "branch.feature-create-pr.gh-merge-base", "main"]);
let generatedPolicy: TextGeneration.PrContentGenerationInput["policy"] = undefined;
let generatedPrTemplate: string | undefined;

const { manager, ghCalls } = yield* makeManager({
serverSettings: {
textGenerationStyle: {
mode: "custom" as const,
customInstructions: "Lead with user impact.",
},
},
textGeneration: {
generatePrContent: (input) => {
generatedPolicy = input.policy;
generatedPrTemplate = input.prTemplate;
return Effect.succeed({
title: "Add stacked git actions",
body: "## What changed?\nAdded stacked git actions.",
});
},
},
ghScenario: {
prListSequence: [
"[]",
Expand All @@ -2225,6 +2306,10 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
expect(result.branch.status).toBe("skipped_not_requested");
expect(result.pr.status).toBe("created");
expect(result.pr.number).toBe(88);
expect(generatedPolicy).toMatchObject({
changeRequestInstructions: "Lead with user impact.",
});
expect(generatedPrTemplate).toBe("## What changed?\n\n## Verification");
expect(ghCalls.filter((call) => call.startsWith("pr list "))).toHaveLength(2);
expect(
ghCalls.some((call) => call.includes("pr create --base main --head feature-create-pr")),
Expand Down
Loading
Loading