Skip to content

Commit 75748ce

Browse files
fix: wire coreEngineDependencies into Factory<DBTCloudProjectIntegration> (#1938)
1 parent 8b743c7 commit 75748ce

3 files changed

Lines changed: 205 additions & 0 deletions

File tree

src/inversify.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,9 @@ container
676676
onDiagnosticsChanged: () => void,
677677
) => {
678678
const { container } = context;
679+
const pythonStrategyFactory = container.get<
680+
(projectRoot: string) => PythonDBTCommandExecutionStrategy
681+
>("Factory<PythonDBTCommandExecutionStrategy>");
679682
return new DBTCloudProjectIntegration(
680683
container.get(DBTCommandExecutionInfrastructure),
681684
container.get(DBTCommandFactory),
@@ -688,6 +691,11 @@ container
688691
deferConfig,
689692
onDiagnosticsChanged,
690693
container.get(DbtCloudVariantDetector),
694+
{
695+
pythonDBTCommandExecutionStrategy: pythonStrategyFactory(projectRoot),
696+
dbtConfiguration: container.get<DBTConfiguration>("DBTConfiguration"),
697+
dbtIntegrationClient: container.get(DbtIntegrationClient),
698+
},
691699
);
692700
};
693701
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import {
2+
DBTCloudProjectIntegration,
3+
DBTCommandExecutionInfrastructure,
4+
DBTCommandFactory,
5+
DBTConfiguration,
6+
DBTDiagnosticData,
7+
DBTTerminal,
8+
DbtCloudVariantDetector,
9+
DbtIntegrationClient,
10+
DeferConfig,
11+
PythonDBTCommandExecutionStrategy,
12+
PythonEnvironmentProvider,
13+
RuntimePythonEnvironment,
14+
} from "@altimateai/dbt-integration";
15+
import { describe, expect, it, jest } from "@jest/globals";
16+
17+
// Documents the runtime contract that Factory<DBTCloudProjectIntegration> must
18+
// satisfy. The Cloud integration only constructs its engine inside
19+
// refreshProjectConfig(), and for any non-Fusion variant (including the
20+
// "no ~/.dbt/dbt_cloud.yml" / "API call failed" case where detect() returns
21+
// null) it routes through CloudCoreCommandIntegration — which requires
22+
// coreEngineDependencies to be passed in. Without those, the engine is never
23+
// constructed and every subsequent dialect call throws "engine not initialized".
24+
25+
const noopTerminal = (): DBTTerminal =>
26+
({
27+
debug: jest.fn(),
28+
info: jest.fn(),
29+
warn: jest.fn(),
30+
error: jest.fn(),
31+
log: jest.fn(),
32+
show: jest.fn(),
33+
}) as unknown as DBTTerminal;
34+
35+
const stubVariantDetectorReturningNull = (): DbtCloudVariantDetector =>
36+
({
37+
detect: jest.fn(async () => null),
38+
}) as unknown as DbtCloudVariantDetector;
39+
40+
const buildIntegration = (opts: {
41+
withCoreEngineDeps: boolean;
42+
}): DBTCloudProjectIntegration => {
43+
const terminal = noopTerminal();
44+
const detector = stubVariantDetectorReturningNull();
45+
const onDiagnosticsChanged = () => {};
46+
const coreEngineDeps = opts.withCoreEngineDeps
47+
? {
48+
pythonDBTCommandExecutionStrategy:
49+
{} as unknown as PythonDBTCommandExecutionStrategy,
50+
dbtConfiguration: {} as unknown as DBTConfiguration,
51+
dbtIntegrationClient: {} as unknown as DbtIntegrationClient,
52+
}
53+
: undefined;
54+
return new DBTCloudProjectIntegration(
55+
{} as unknown as DBTCommandExecutionInfrastructure,
56+
{} as unknown as DBTCommandFactory,
57+
() => ({}) as never,
58+
{} as unknown as RuntimePythonEnvironment,
59+
{} as unknown as PythonEnvironmentProvider,
60+
terminal,
61+
"/tmp/project",
62+
[] as DBTDiagnosticData[],
63+
{} as DeferConfig,
64+
onDiagnosticsChanged,
65+
detector,
66+
coreEngineDeps,
67+
);
68+
};
69+
70+
describe("DBTCloudProjectIntegration coreEngineDependencies contract", () => {
71+
it("createEngine throws on a non-Fusion variant when coreEngineDependencies is omitted (the v0.61.0 regression — Factory<DBTCloudProjectIntegration> must wire these up)", () => {
72+
const integration = buildIntegration({ withCoreEngineDeps: false });
73+
expect(() =>
74+
(
75+
integration as unknown as {
76+
createEngine: () => unknown;
77+
}
78+
).createEngine(),
79+
).toThrow(/cannot construct CloudCoreCommandIntegration/);
80+
});
81+
82+
it("createEngine clears the coreEngineDependencies guard once they are provided", () => {
83+
// Pinpoints the guard at the top of createEngine. Construction past the
84+
// guard reaches into CloudCoreCommandIntegration's super-chain which needs
85+
// real DBTCommandExecutionInfrastructure / RuntimePythonEnvironment
86+
// wiring; here we only prove the missing-deps gate is no longer hit.
87+
const integration = buildIntegration({ withCoreEngineDeps: true });
88+
expect(() =>
89+
(
90+
integration as unknown as {
91+
createEngine: () => unknown;
92+
}
93+
).createEngine(),
94+
).not.toThrow(/cannot construct CloudCoreCommandIntegration/);
95+
});
96+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
DBTCloudProjectIntegration,
3+
DBTDiagnosticData,
4+
DeferConfig,
5+
} from "@altimateai/dbt-integration";
6+
import { describe, expect, it, jest } from "@jest/globals";
7+
8+
// The shared vscode mock at src/test/mock/vscode.ts has no `env` or
9+
// `extensions` namespaces. Stub them in before importing inversify.config —
10+
// the Cloud factory transitively constructs TelemetryService (reads env.appName
11+
// + creates a TelemetryReporter, which calls env.createTelemetryLogger) and
12+
// VSCodeRuntimePythonEnvironmentProvider (reads extensions.getExtension).
13+
import * as vscodeMock from "../mock/vscode";
14+
const vscodeMockAny = vscodeMock as Record<string, unknown>;
15+
vscodeMockAny.env = {
16+
appName: "vscode-test",
17+
machineId: "test-machine",
18+
sessionId: "test-session",
19+
isTelemetryEnabled: false,
20+
onDidChangeTelemetryEnabled: () => ({ dispose: () => {} }),
21+
createTelemetryLogger: () => ({
22+
logUsage: () => {},
23+
logError: () => {},
24+
dispose: () => {},
25+
onDidChangeEnableStates: () => ({ dispose: () => {} }),
26+
}),
27+
};
28+
// VSCodeDBTTerminal calls outputChannel.debug/info/warn/error — the shared
29+
// mock channel only has append/appendLine/clear/show/hide/dispose.
30+
const sharedWindow = vscodeMockAny.window as Record<string, unknown>;
31+
sharedWindow.createOutputChannel = jest.fn(() => ({
32+
append: jest.fn(),
33+
appendLine: jest.fn(),
34+
clear: jest.fn(),
35+
show: jest.fn(),
36+
hide: jest.fn(),
37+
dispose: jest.fn(),
38+
debug: jest.fn(),
39+
info: jest.fn(),
40+
warn: jest.fn(),
41+
error: jest.fn(),
42+
trace: jest.fn(),
43+
replace: jest.fn(),
44+
name: "Log - dbt",
45+
logLevel: 1,
46+
onDidChangeLogLevel: () => ({ dispose: () => {} }),
47+
}));
48+
vscodeMockAny.extensions = {
49+
getExtension: jest.fn(() => ({
50+
isActive: true,
51+
activate: () => Promise.resolve(),
52+
exports: {
53+
settings: {
54+
getExecutionDetails: () => ({ execCommand: ["python"] }),
55+
onDidChangeExecutionDetails: () => ({ dispose: () => {} }),
56+
},
57+
environment: {
58+
getEnvironmentPaths: async () => [],
59+
getEnvironmentDetails: async () => ({ executable: { uri: undefined } }),
60+
},
61+
},
62+
})),
63+
};
64+
65+
import { container } from "../../inversify.config";
66+
67+
describe("Factory<DBTCloudProjectIntegration>", () => {
68+
it("constructs the Cloud integration with coreEngineDependencies wired up so non-Fusion variants can build a CloudCoreCommandIntegration engine", () => {
69+
type CloudFactory = (
70+
projectRoot: string,
71+
projectConfigDiagnostics: DBTDiagnosticData[],
72+
deferConfig: DeferConfig,
73+
onDiagnosticsChanged: () => void,
74+
) => DBTCloudProjectIntegration;
75+
76+
const factory = container.get<CloudFactory>(
77+
"Factory<DBTCloudProjectIntegration>",
78+
);
79+
const integration = factory(
80+
"/tmp/project",
81+
[],
82+
{} as DeferConfig,
83+
() => {},
84+
);
85+
86+
const deps = (
87+
integration as unknown as {
88+
coreEngineDependencies?: {
89+
pythonDBTCommandExecutionStrategy: unknown;
90+
dbtConfiguration: unknown;
91+
dbtIntegrationClient: unknown;
92+
};
93+
}
94+
).coreEngineDependencies;
95+
96+
expect(deps).toBeDefined();
97+
expect(deps?.pythonDBTCommandExecutionStrategy).toBeDefined();
98+
expect(deps?.dbtConfiguration).toBeDefined();
99+
expect(deps?.dbtIntegrationClient).toBeDefined();
100+
});
101+
});

0 commit comments

Comments
 (0)