Skip to content

Commit a44cd3a

Browse files
authored
Merge branch 'main' into fix-trial-fim-import
2 parents 97e0c3c + 15b16a1 commit a44cd3a

File tree

14 files changed

+2437
-271
lines changed

14 files changed

+2437
-271
lines changed

.github/workflows/pr_checks.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ jobs:
408408
run: |
409409
cd core
410410
npm test
411+
npm run vitest
411412
env:
412413
IGNORE_API_KEY_TESTS: ${{ github.event.pull_request.head.repo.fork == true || github.actor == 'dependabot[bot]' }}
413414
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { FQSN, SecretResult, SecretType } from "@continuedev/config-yaml";
2+
import {
3+
afterEach,
4+
beforeEach,
5+
describe,
6+
expect,
7+
Mock,
8+
test,
9+
vi,
10+
} from "vitest";
11+
import { IDE } from "../..";
12+
import { ControlPlaneClient } from "../../control-plane/client";
13+
import { LocalPlatformClient } from "./LocalPlatformClient";
14+
15+
vi.mock("../../util/paths", { spy: true });
16+
17+
describe("LocalPlatformClient", () => {
18+
const testFQSN: FQSN = {
19+
packageSlugs: [
20+
{
21+
ownerSlug: "test-owner-slug",
22+
packageSlug: "test-package-slug",
23+
},
24+
],
25+
secretName: "TEST_CONTINUE_SECRET_KEY",
26+
};
27+
const testFQSN2: FQSN = {
28+
packageSlugs: [
29+
{
30+
ownerSlug: "test-owner-slug-2",
31+
packageSlug: "test-package-slug-2",
32+
},
33+
],
34+
secretName: "TEST_WORKSPACE_SECRET_KEY",
35+
};
36+
37+
const testResolvedFQSN: SecretResult = {
38+
found: true,
39+
fqsn: testFQSN,
40+
secretLocation: {
41+
secretName: testFQSN.secretName,
42+
secretType: SecretType.Organization,
43+
orgSlug: "test-org-slug",
44+
},
45+
};
46+
47+
let testControlPlaneClient: ControlPlaneClient;
48+
let testIde: IDE;
49+
beforeEach(
50+
/**dynamic import before each test for test isolation */
51+
async () => {
52+
const testFixtures = await import("../../test/fixtures");
53+
testControlPlaneClient = testFixtures.testControlPlaneClient;
54+
testIde = testFixtures.testIde;
55+
},
56+
);
57+
58+
let secretValue: string;
59+
let envKeyValues: Record<string, unknown>;
60+
let envKeyValuesString: string;
61+
beforeEach(
62+
/**generate unique env key value pairs for each test */
63+
() => {
64+
secretValue = Math.floor(Math.random() * 100) + "";
65+
envKeyValues = {
66+
TEST_CONTINUE_SECRET_KEY: secretValue,
67+
TEST_WORKSPACE_SECRET_KEY: secretValue + "-workspace",
68+
};
69+
envKeyValuesString = Object.entries(envKeyValues)
70+
.map(([key, value]) => `${key}=${value}`)
71+
.join("\n");
72+
},
73+
);
74+
75+
afterEach(() => {
76+
vi.resetAllMocks();
77+
vi.restoreAllMocks();
78+
vi.resetModules(); // clear dynamic imported module cache
79+
});
80+
81+
test("should not be able to resolve FQSNs if they do not exist", async () => {
82+
const localPlatformClient = new LocalPlatformClient(
83+
null,
84+
testControlPlaneClient,
85+
testIde,
86+
);
87+
const resolvedFQSNs = await localPlatformClient.resolveFQSNs([testFQSN]);
88+
89+
expect(resolvedFQSNs.length).toBeGreaterThan(0);
90+
expect(resolvedFQSNs[0]?.found).toBe(false);
91+
});
92+
93+
test("should be able to resolve FQSNs if they exist", async () => {
94+
testControlPlaneClient.resolveFQSNs = vi.fn(async () => [testResolvedFQSN]);
95+
const localPlatformClient = new LocalPlatformClient(
96+
null,
97+
testControlPlaneClient,
98+
testIde,
99+
);
100+
const resolvedFQSNs = await localPlatformClient.resolveFQSNs([testFQSN]);
101+
expect(testControlPlaneClient.resolveFQSNs).toHaveBeenCalled();
102+
expect(resolvedFQSNs).toEqual([testResolvedFQSN]);
103+
expect(resolvedFQSNs[0]?.found).toBe(true);
104+
});
105+
106+
describe("searches for secrets in local .env files", () => {
107+
let getContinueDotEnv: Mock;
108+
beforeEach(async () => {
109+
const utilPaths = await import("../../util/paths");
110+
getContinueDotEnv = vi.fn(() => envKeyValues);
111+
utilPaths.getContinueDotEnv = getContinueDotEnv;
112+
});
113+
114+
test("should be able to get secrets from ~/.continue/.env files", async () => {
115+
const localPlatformClient = new LocalPlatformClient(
116+
null,
117+
testControlPlaneClient,
118+
testIde,
119+
);
120+
const resolvedFQSNs = await localPlatformClient.resolveFQSNs([testFQSN]);
121+
expect(getContinueDotEnv).toHaveBeenCalled();
122+
expect(resolvedFQSNs.length).toBe(1);
123+
expect(
124+
(resolvedFQSNs[0] as SecretResult & { value: unknown })?.value,
125+
).toBe(secretValue);
126+
console.log("debug1 resolved fqsn", resolvedFQSNs);
127+
});
128+
});
129+
130+
describe("should be able to get secrets from workspace .env files", () => {
131+
test("should get secrets from <workspace>/.continue/.env and <workspace>/.env", async () => {
132+
const originalIdeFileExists = testIde.fileExists;
133+
testIde.fileExists = vi.fn(async (fileUri: string) =>
134+
fileUri.includes(".env") ? true : originalIdeFileExists(fileUri),
135+
);
136+
137+
const originalIdeReadFile = testIde.readFile;
138+
const randomValueForContinueDirDotEnv =
139+
"continue-dir-" + Math.floor(Math.random() * 100);
140+
const randomValueForWorkspaceDotEnv =
141+
"dotenv-" + Math.floor(Math.random() * 100);
142+
143+
testIde.readFile = vi.fn(async (fileUri: string) => {
144+
// fileUri should contain .continue/.env and not .env
145+
if (fileUri.match(/.*\.continue\/\.env.*/gi)?.length) {
146+
return (
147+
envKeyValuesString.split("\n")[0] + randomValueForContinueDirDotEnv
148+
);
149+
}
150+
// filUri should contain .env and not .continue/.env
151+
else if (fileUri.match(/.*(?<!\.continue\/)\.env.*/gi)?.length) {
152+
return (
153+
envKeyValuesString.split("\n")[1] + randomValueForWorkspaceDotEnv
154+
);
155+
}
156+
return originalIdeReadFile(fileUri);
157+
});
158+
159+
const localPlatformClient = new LocalPlatformClient(
160+
null,
161+
testControlPlaneClient,
162+
testIde,
163+
);
164+
const resolvedFQSNs = await localPlatformClient.resolveFQSNs([
165+
testFQSN,
166+
testFQSN2,
167+
]);
168+
169+
// both the secrets should be present as they are retrieved from different files
170+
171+
expect(resolvedFQSNs.length).toBe(2);
172+
173+
const continueDirSecretValue = (
174+
resolvedFQSNs[0] as SecretResult & { value: unknown }
175+
)?.value;
176+
const dotEnvSecretValue = (
177+
resolvedFQSNs[1] as SecretResult & { value: unknown }
178+
)?.value;
179+
expect(continueDirSecretValue).toContain(secretValue);
180+
expect(continueDirSecretValue).toContain(randomValueForContinueDirDotEnv);
181+
expect(dotEnvSecretValue).toContain(secretValue + "-workspace");
182+
expect(dotEnvSecretValue).toContain(randomValueForWorkspaceDotEnv);
183+
});
184+
185+
test("should first get secrets from <workspace>/.continue/.env and then <workspace>/.env", async () => {
186+
const originalIdeFileExists = testIde.fileExists;
187+
testIde.fileExists = vi.fn(async (fileUri: string) =>
188+
fileUri.includes(".env") ? true : originalIdeFileExists(fileUri),
189+
);
190+
191+
const randomValueForContinueDirDotEnv =
192+
"continue-dir-" + Math.floor(Math.random() * 100);
193+
const randomValueForWorkspaceDotEnv =
194+
"dotenv-" + Math.floor(Math.random() * 100);
195+
196+
const originalIdeReadFile = testIde.readFile;
197+
testIde.readFile = vi.fn(async (fileUri: string) => {
198+
// fileUri should contain .continue/.env and not .env
199+
if (fileUri.match(/.*\.continue\/\.env.*/gi)?.length) {
200+
return (
201+
envKeyValuesString.split("\n")[0] + randomValueForContinueDirDotEnv
202+
);
203+
}
204+
// filUri should contain .env and not .continue/.env
205+
else if (fileUri.match(/.*(?<!\.continue\/)\.env.*/gi)?.length) {
206+
return (
207+
envKeyValuesString.split("\n")[0] + randomValueForWorkspaceDotEnv
208+
);
209+
}
210+
return originalIdeReadFile(fileUri);
211+
});
212+
213+
const localPlatformClient = new LocalPlatformClient(
214+
null,
215+
testControlPlaneClient,
216+
testIde,
217+
);
218+
const resolvedFQSNs = await localPlatformClient.resolveFQSNs([testFQSN]);
219+
220+
expect(resolvedFQSNs.length).toBe(1);
221+
expect(
222+
(resolvedFQSNs[0] as SecretResult & { value: unknown })?.value,
223+
).toContain(secretValue);
224+
// we check that workspace <workspace>.continue/.env does not override the <workspace>/.env secret
225+
expect(
226+
(resolvedFQSNs[0] as SecretResult & { value: unknown })?.value,
227+
).toContain(randomValueForContinueDirDotEnv);
228+
expect(
229+
(resolvedFQSNs[0] as SecretResult & { value: unknown })?.value,
230+
).not.toContain(randomValueForWorkspaceDotEnv);
231+
});
232+
});
233+
});

core/config/yaml/LocalPlatformClient.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ export class LocalPlatformClient implements PlatformClient {
1717
private readonly ide: IDE,
1818
) {}
1919

20+
/**
21+
* searches for the first valid secret file in order of ~/.continue/.env, <workspace>/.continue/.env, <workspace>/.env
22+
*/
2023
private async findSecretInEnvFiles(
2124
fqsn: FQSN,
2225
): Promise<SecretResult | undefined> {
2326
const secretValue =
2427
this.findSecretInLocalEnvFile(fqsn) ??
25-
(await this.findSecretInWorkspaceEnvFiles(fqsn));
28+
(await this.findSecretInWorkspaceEnvFiles(fqsn, true)) ??
29+
(await this.findSecretInWorkspaceEnvFiles(fqsn, false));
2630

2731
if (secretValue) {
2832
return {
@@ -52,12 +56,16 @@ export class LocalPlatformClient implements PlatformClient {
5256

5357
private async findSecretInWorkspaceEnvFiles(
5458
fqsn: FQSN,
59+
insideContinue: boolean,
5560
): Promise<string | undefined> {
5661
try {
5762
const workspaceDirs = await this.ide.getWorkspaceDirs();
58-
5963
for (const folder of workspaceDirs) {
60-
const envFilePath = joinPathsToUri(folder, ".env");
64+
const envFilePath = joinPathsToUri(
65+
folder,
66+
insideContinue ? ".continue" : "",
67+
".env",
68+
);
6169
try {
6270
const fileExists = await this.ide.fileExists(envFilePath);
6371
if (fileExists) {

core/jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,7 @@ export default {
2929
globalSetup: "<rootDir>/test/jest.global-setup.ts",
3030
setupFilesAfterEnv: ["<rootDir>/test/jest.setup-after-env.js"],
3131
maxWorkers: 1, // equivalent to CLI --runInBand
32+
modulePathIgnorePatterns: [
33+
"<rootDir>/config/yaml/LocalPlatformClient.test.ts",
34+
],
3235
};

core/nextEdit/NextEditProvider.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ export class NextEditProvider {
7878
if (llm instanceof OpenAI) {
7979
llm.useLegacyCompletionsEndpoint = true;
8080
}
81+
// TODO: Resolve import error with TRIAL_FIM_MODEL
82+
// else if (
83+
// llm.providerName === "free-trial" &&
84+
// llm.model !== TRIAL_FIM_MODEL
85+
// ) {
86+
// llm.model = TRIAL_FIM_MODEL;
87+
// }
8188

8289
return llm;
8390
}

0 commit comments

Comments
 (0)