-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathsdkTestContext.ts
More file actions
106 lines (89 loc) · 4.02 KB
/
sdkTestContext.ts
File metadata and controls
106 lines (89 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
import fs, { realpathSync } from "fs";
import { rm } from "fs/promises";
import os from "os";
import { basename, dirname, join, resolve } from "path";
import { rimraf } from "rimraf";
import { fileURLToPath } from "url";
import { afterAll, afterEach, beforeEach, onTestFailed, TestContext } from "vitest";
import { CopilotClient } from "../../../src";
import { CapiProxy } from "./CapiProxy";
import { retry } from "./sdkTestHelper";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const SNAPSHOTS_DIR = resolve(__dirname, "../../../../test/snapshots");
export const CLI_PATH =
process.env.COPILOT_CLI_PATH ||
resolve(__dirname, "../../../node_modules/@github/copilot/index.js");
export async function createSdkTestContext({
logLevel,
}: { logLevel?: "error" | "none" | "warning" | "info" | "debug" | "all" } = {}) {
const homeDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-config-")));
const workDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-work-")));
const openAiEndpoint = new CapiProxy();
const proxyUrl = await openAiEndpoint.start();
const env = {
...process.env,
COPILOT_API_URL: proxyUrl,
// TODO: I'm not convinced the SDK should default to using whatever config you happen to have in your homedir.
// The SDK config should be independent of the regular CLI app. Likewise it shouldn't mix sessions from the
// SDK with those from the CLI app, at least not by default.
XDG_CONFIG_HOME: homeDir,
XDG_STATE_HOME: homeDir,
};
const copilotClient = new CopilotClient({
cliPath: CLI_PATH,
cwd: workDir,
env,
logLevel: logLevel || "error",
// Use fake token in CI to allow cached responses without real auth
githubToken: process.env.CI === "true" ? "fake-token-for-e2e-tests" : undefined,
});
const harness = { homeDir, workDir, openAiEndpoint, copilotClient, env };
// Track if any test fails to avoid writing corrupted snapshots
let anyTestFailed = false;
// Wire up to Vitest lifecycle
beforeEach(async (testContext) => {
// Must be inside beforeEach - vitest requires test context
onTestFailed(() => {
anyTestFailed = true;
});
await openAiEndpoint.updateConfig({
filePath: getTrafficCapturePath(testContext),
workDir,
testInfo: {
file: testContext.task.file.filepath,
line: testContext.task.location?.line,
},
});
});
afterEach(async () => {
// Empty directories but leave them in place for next test
await rimraf([join(homeDir, "*"), join(workDir, "*")], { glob: true });
});
afterAll(async () => {
await copilotClient.stop();
await openAiEndpoint.stop(anyTestFailed);
await rmDir("remove e2e test homeDir", homeDir);
await rmDir("remove e2e test workDir", workDir);
});
return harness;
}
function getTrafficCapturePath(testContext: TestContext): string {
const testFilePath = testContext.task.file.filepath;
const suffix = ".test.ts";
if (!testFilePath.endsWith(suffix)) {
throw new Error(
`Test file path does not end with expected suffix '${suffix}': ${testFilePath}`
);
}
// Convert to snake_case for cross-SDK snapshot compatibility
const testFileName = basename(testFilePath, suffix).replace(/-/g, "_");
const taskNameAsFilename = testContext.task.name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
return join(SNAPSHOTS_DIR, testFileName, `${taskNameAsFilename}.yaml`);
}
function rmDir(message: string, path: string): Promise<void> {
return retry(message, () => rm(path, { recursive: true, force: true }), 5, 2000);
}