@lightdash/cli 2.228.1 → 2.229.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo.build +1 -0
- package/package.json +4 -4
- package/dist/.tsbuildinfo +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -72
- package/dist/dbt/context.test.d.ts +0 -2
- package/dist/dbt/context.test.d.ts.map +0 -1
- package/dist/dbt/context.test.js +0 -152
- package/dist/dbt/manifest.test.d.ts +0 -2
- package/dist/dbt/manifest.test.d.ts.map +0 -1
- package/dist/dbt/manifest.test.js +0 -170
- package/dist/dbt/models.test.d.ts +0 -2
- package/dist/dbt/models.test.d.ts.map +0 -1
- package/dist/dbt/models.test.js +0 -139
- package/dist/dbt/profiles.test.d.ts +0 -2
- package/dist/dbt/profiles.test.d.ts.map +0 -1
- package/dist/dbt/profiles.test.js +0 -50
- package/dist/dbt/targets/athena.test.d.ts +0 -2
- package/dist/dbt/targets/athena.test.d.ts.map +0 -1
- package/dist/dbt/targets/athena.test.js +0 -78
- package/dist/dbt/targets/duckdb.test.d.ts +0 -2
- package/dist/dbt/targets/duckdb.test.d.ts.map +0 -1
- package/dist/dbt/targets/duckdb.test.js +0 -149
- package/dist/dbt/targets/redshift.test.d.ts +0 -2
- package/dist/dbt/targets/redshift.test.d.ts.map +0 -1
- package/dist/dbt/targets/redshift.test.js +0 -198
- package/dist/dbt/templating.test.d.ts +0 -2
- package/dist/dbt/templating.test.d.ts.map +0 -1
- package/dist/dbt/templating.test.js +0 -51
- package/dist/dbt/validation.test.d.ts +0 -2
- package/dist/dbt/validation.test.d.ts.map +0 -1
- package/dist/dbt/validation.test.js +0 -67
- package/dist/handlers/apps/appCodeFiles.test.d.ts +0 -2
- package/dist/handlers/apps/appCodeFiles.test.d.ts.map +0 -1
- package/dist/handlers/apps/appCodeFiles.test.js +0 -391
- package/dist/handlers/apps/appsDownload.test.d.ts +0 -2
- package/dist/handlers/apps/appsDownload.test.d.ts.map +0 -1
- package/dist/handlers/apps/appsDownload.test.js +0 -639
- package/dist/handlers/apps/build.test.d.ts +0 -2
- package/dist/handlers/apps/build.test.d.ts.map +0 -1
- package/dist/handlers/apps/build.test.js +0 -252
- package/dist/handlers/apps/createApp.test.d.ts +0 -2
- package/dist/handlers/apps/createApp.test.d.ts.map +0 -1
- package/dist/handlers/apps/createApp.test.js +0 -340
- package/dist/handlers/apps/pathContainment.test.d.ts +0 -2
- package/dist/handlers/apps/pathContainment.test.d.ts.map +0 -1
- package/dist/handlers/apps/pathContainment.test.js +0 -66
- package/dist/handlers/apps/preview.test.d.ts +0 -2
- package/dist/handlers/apps/preview.test.d.ts.map +0 -1
- package/dist/handlers/apps/preview.test.js +0 -353
- package/dist/handlers/apps/previewProxy.test.d.ts +0 -2
- package/dist/handlers/apps/previewProxy.test.d.ts.map +0 -1
- package/dist/handlers/apps/previewProxy.test.js +0 -178
- package/dist/handlers/apps/scaffolding.test.d.ts +0 -2
- package/dist/handlers/apps/scaffolding.test.d.ts.map +0 -1
- package/dist/handlers/apps/scaffolding.test.js +0 -151
- package/dist/handlers/apps/uploadRetry.test.d.ts +0 -2
- package/dist/handlers/apps/uploadRetry.test.d.ts.map +0 -1
- package/dist/handlers/apps/uploadRetry.test.js +0 -103
- package/dist/handlers/apps/validate.test.d.ts +0 -2
- package/dist/handlers/apps/validate.test.d.ts.map +0 -1
- package/dist/handlers/apps/validate.test.js +0 -526
- package/dist/handlers/compile.test.d.ts +0 -2
- package/dist/handlers/compile.test.d.ts.map +0 -1
- package/dist/handlers/compile.test.js +0 -151
- package/dist/handlers/compileProject.test.d.ts +0 -2
- package/dist/handlers/compileProject.test.d.ts.map +0 -1
- package/dist/handlers/compileProject.test.js +0 -637
- package/dist/handlers/compileWarehouseColumnValidation.test.d.ts +0 -2
- package/dist/handlers/compileWarehouseColumnValidation.test.d.ts.map +0 -1
- package/dist/handlers/compileWarehouseColumnValidation.test.js +0 -493
- package/dist/handlers/connectSnowflake.test.d.ts +0 -2
- package/dist/handlers/connectSnowflake.test.d.ts.map +0 -1
- package/dist/handlers/connectSnowflake.test.js +0 -320
- package/dist/handlers/contentAsCode/fileNames.test.d.ts +0 -2
- package/dist/handlers/contentAsCode/fileNames.test.d.ts.map +0 -1
- package/dist/handlers/contentAsCode/fileNames.test.js +0 -79
- package/dist/handlers/contentAsCode/registry.test.d.ts +0 -2
- package/dist/handlers/contentAsCode/registry.test.d.ts.map +0 -1
- package/dist/handlers/contentAsCode/registry.test.js +0 -18
- package/dist/handlers/contentAsCode/resource.test.d.ts +0 -2
- package/dist/handlers/contentAsCode/resource.test.d.ts.map +0 -1
- package/dist/handlers/contentAsCode/resource.test.js +0 -121
- package/dist/handlers/dbt/getDbtVersion.test.d.ts +0 -2
- package/dist/handlers/dbt/getDbtVersion.test.d.ts.map +0 -1
- package/dist/handlers/dbt/getDbtVersion.test.js +0 -106
- package/dist/handlers/deploy.test.d.ts +0 -2
- package/dist/handlers/deploy.test.d.ts.map +0 -1
- package/dist/handlers/deploy.test.js +0 -244
- package/dist/handlers/download.test.d.ts +0 -2
- package/dist/handlers/download.test.d.ts.map +0 -1
- package/dist/handlers/download.test.js +0 -1739
- package/dist/handlers/exportChartImage.test.d.ts +0 -2
- package/dist/handlers/exportChartImage.test.d.ts.map +0 -1
- package/dist/handlers/exportChartImage.test.js +0 -55
- package/dist/handlers/homepagesAsCode.test.d.ts +0 -2
- package/dist/handlers/homepagesAsCode.test.d.ts.map +0 -1
- package/dist/handlers/homepagesAsCode.test.js +0 -72
- package/dist/handlers/lint.test.d.ts +0 -2
- package/dist/handlers/lint.test.d.ts.map +0 -1
- package/dist/handlers/lint.test.js +0 -221
- package/dist/handlers/login.test.d.ts +0 -2
- package/dist/handlers/login.test.d.ts.map +0 -1
- package/dist/handlers/login.test.js +0 -24
- package/dist/handlers/metadataFile.test.d.ts +0 -2
- package/dist/handlers/metadataFile.test.d.ts.map +0 -1
- package/dist/handlers/metadataFile.test.js +0 -46
- package/dist/handlers/organizationContent/customRoles.test.d.ts +0 -2
- package/dist/handlers/organizationContent/customRoles.test.d.ts.map +0 -1
- package/dist/handlers/organizationContent/customRoles.test.js +0 -179
- package/dist/handlers/organizationContent/groups.test.d.ts +0 -2
- package/dist/handlers/organizationContent/groups.test.d.ts.map +0 -1
- package/dist/handlers/organizationContent/groups.test.js +0 -135
- package/dist/handlers/organizationContent/index.test.d.ts +0 -2
- package/dist/handlers/organizationContent/index.test.d.ts.map +0 -1
- package/dist/handlers/organizationContent/index.test.js +0 -212
- package/dist/handlers/organizationContent/themes.test.d.ts +0 -2
- package/dist/handlers/organizationContent/themes.test.d.ts.map +0 -1
- package/dist/handlers/organizationContent/themes.test.js +0 -180
- package/dist/handlers/organizationContent/users.test.d.ts +0 -2
- package/dist/handlers/organizationContent/users.test.d.ts.map +0 -1
- package/dist/handlers/organizationContent/users.test.js +0 -155
- package/dist/handlers/preAggregateAudit.test.d.ts +0 -2
- package/dist/handlers/preAggregateAudit.test.d.ts.map +0 -1
- package/dist/handlers/preAggregateAudit.test.js +0 -102
- package/dist/handlers/renameHandler.test.d.ts +0 -2
- package/dist/handlers/renameHandler.test.d.ts.map +0 -1
- package/dist/handlers/renameHandler.test.js +0 -189
- package/dist/handlers/resolveProjectFlag.test.d.ts +0 -2
- package/dist/handlers/resolveProjectFlag.test.d.ts.map +0 -1
- package/dist/handlers/resolveProjectFlag.test.js +0 -48
- package/dist/handlers/selectProject.test.d.ts +0 -2
- package/dist/handlers/selectProject.test.d.ts.map +0 -1
- package/dist/handlers/selectProject.test.js +0 -224
- package/dist/handlers/setProject.test.d.ts +0 -2
- package/dist/handlers/setProject.test.d.ts.map +0 -1
- package/dist/handlers/setProject.test.js +0 -63
- package/dist/handlers/slugUpdate.test.d.ts +0 -2
- package/dist/handlers/slugUpdate.test.d.ts.map +0 -1
- package/dist/handlers/slugUpdate.test.js +0 -265
- package/dist/handlers/timestampConversion.test.d.ts +0 -2
- package/dist/handlers/timestampConversion.test.d.ts.map +0 -1
- package/dist/handlers/timestampConversion.test.js +0 -87
- package/dist/handlers/upgradeCheck.test.d.ts +0 -2
- package/dist/handlers/upgradeCheck.test.d.ts.map +0 -1
- package/dist/handlers/upgradeCheck.test.js +0 -227
- package/dist/handlers/validate.test.d.ts +0 -2
- package/dist/handlers/validate.test.d.ts.map +0 -1
- package/dist/handlers/validate.test.js +0 -318
- package/dist/handlers/validateSpaceFilter.test.d.ts +0 -2
- package/dist/handlers/validateSpaceFilter.test.d.ts.map +0 -1
- package/dist/handlers/validateSpaceFilter.test.js +0 -79
- package/dist/handlers/warehouseConnection.test.d.ts +0 -2
- package/dist/handlers/warehouseConnection.test.d.ts.map +0 -1
- package/dist/handlers/warehouseConnection.test.js +0 -75
- package/dist/lightdash-config/lightdash-config.test.d.ts +0 -2
- package/dist/lightdash-config/lightdash-config.test.d.ts.map +0 -1
- package/dist/lightdash-config/lightdash-config.test.js +0 -69
- package/dist/prompts/fuzzySearchPrompt.test.d.ts +0 -2
- package/dist/prompts/fuzzySearchPrompt.test.d.ts.map +0 -1
- package/dist/prompts/fuzzySearchPrompt.test.js +0 -53
- package/dist/refsArgument.test.d.ts +0 -2
- package/dist/refsArgument.test.d.ts.map +0 -1
- package/dist/refsArgument.test.js +0 -42
- package/dist/releaseSafety/index.test.d.ts +0 -2
- package/dist/releaseSafety/index.test.d.ts.map +0 -1
- package/dist/releaseSafety/index.test.js +0 -154
- package/dist/releaseSafety/version.test.d.ts +0 -2
- package/dist/releaseSafety/version.test.d.ts.map +0 -1
- package/dist/releaseSafety/version.test.js +0 -22
- package/dist/skills.test.d.ts +0 -2
- package/dist/skills.test.d.ts.map +0 -1
- package/dist/skills.test.js +0 -112
- package/dist/terminal/contentAsCodeOutput.test.d.ts +0 -2
- package/dist/terminal/contentAsCodeOutput.test.d.ts.map +0 -1
- package/dist/terminal/contentAsCodeOutput.test.js +0 -231
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const common_1 = require("@lightdash/common");
|
|
5
|
-
const fs_1 = require("fs");
|
|
6
|
-
const os = tslib_1.__importStar(require("os"));
|
|
7
|
-
const path = tslib_1.__importStar(require("path"));
|
|
8
|
-
const appCodeFiles_1 = require("./appCodeFiles");
|
|
9
|
-
const validate_1 = require("./validate");
|
|
10
|
-
const semanticLayer = [
|
|
11
|
-
'models:',
|
|
12
|
-
' - name: orders',
|
|
13
|
-
' meta:',
|
|
14
|
-
' metrics:',
|
|
15
|
-
' total_revenue:',
|
|
16
|
-
' type: sum',
|
|
17
|
-
' columns:',
|
|
18
|
-
' - name: status',
|
|
19
|
-
].join('\n');
|
|
20
|
-
const defaultManifest = {
|
|
21
|
-
codeVersion: 1,
|
|
22
|
-
projectUuid: 'project-uuid',
|
|
23
|
-
slug: 'orders-app',
|
|
24
|
-
version: 1,
|
|
25
|
-
name: 'Orders app',
|
|
26
|
-
description: '',
|
|
27
|
-
template: null,
|
|
28
|
-
downloadedAt: '2026-08-01T00:00:00.000Z',
|
|
29
|
-
externalConnections: [{ alias: 'stripe', connectionSlug: 'stripe-api' }],
|
|
30
|
-
};
|
|
31
|
-
const validSource = `
|
|
32
|
-
import { query } from '@lightdash/query-sdk';
|
|
33
|
-
query('orders').dimensions(['status']).metrics(['total_revenue']);
|
|
34
|
-
lightdash.externalFetch('stripe', { path: '/charges' });
|
|
35
|
-
`;
|
|
36
|
-
const makeApp = async (args) => {
|
|
37
|
-
const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-validate-'));
|
|
38
|
-
const code = {
|
|
39
|
-
manifest: args?.manifest ?? defaultManifest,
|
|
40
|
-
files: [
|
|
41
|
-
{
|
|
42
|
-
path: 'src/App.tsx',
|
|
43
|
-
contentBase64: Buffer.from(args?.source ?? validSource).toString('base64'),
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
};
|
|
47
|
-
await (0, appCodeFiles_1.writeBundleToDir)(dir, code);
|
|
48
|
-
const layout = args?.semanticLayout ?? 'sharded';
|
|
49
|
-
if (layout === 'legacy') {
|
|
50
|
-
const contextDir = path.join(dir, '.lightdash', 'context');
|
|
51
|
-
await fs_1.promises.mkdir(contextDir, { recursive: true });
|
|
52
|
-
await fs_1.promises.writeFile(path.join(contextDir, 'semantic-layer.yml'), semanticLayer);
|
|
53
|
-
}
|
|
54
|
-
else if (layout === 'sharded') {
|
|
55
|
-
const modelsDir = path.join(dir, '.lightdash', 'context', 'models');
|
|
56
|
-
await fs_1.promises.mkdir(modelsDir, { recursive: true });
|
|
57
|
-
await Promise.all([
|
|
58
|
-
fs_1.promises.writeFile(path.join(dir, '.lightdash', 'context', 'semantic-layer.yml'), '# See models/'),
|
|
59
|
-
fs_1.promises.writeFile(path.join(modelsDir, 'orders.yml'), semanticLayer),
|
|
60
|
-
]);
|
|
61
|
-
}
|
|
62
|
-
if (args?.packageJson !== undefined) {
|
|
63
|
-
await fs_1.promises.writeFile(path.join(dir, 'package.json'), args.packageJson);
|
|
64
|
-
}
|
|
65
|
-
if (args?.lockfile !== undefined) {
|
|
66
|
-
await fs_1.promises.writeFile(path.join(dir, 'pnpm-lock.yaml'), args.lockfile);
|
|
67
|
-
}
|
|
68
|
-
return dir;
|
|
69
|
-
};
|
|
70
|
-
const unusedLiveLoader = () => {
|
|
71
|
-
throw new Error('live semantic layer should not be loaded');
|
|
72
|
-
};
|
|
73
|
-
const liveIndex = (0, common_1.buildDataAppExploreIndexFromModelFiles)([
|
|
74
|
-
{ path: 'orders.yml', content: semanticLayer },
|
|
75
|
-
]);
|
|
76
|
-
describe('validateLocalDataApp', () => {
|
|
77
|
-
it.each(['sharded', 'legacy'])('validates a correct app against a %s offline semantic layer', async (semanticLayout) => {
|
|
78
|
-
const dir = await makeApp({ semanticLayout });
|
|
79
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
80
|
-
live: false,
|
|
81
|
-
loadLiveIndex: unusedLiveLoader,
|
|
82
|
-
});
|
|
83
|
-
expect(result.valid).toBe(true);
|
|
84
|
-
expect(result.errors).toEqual([]);
|
|
85
|
-
expect(result.coverage).toMatchObject({
|
|
86
|
-
callSites: 2,
|
|
87
|
-
fullyResolved: 2,
|
|
88
|
-
unanalyzed: 0,
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
it('reports semantic errors and undeclared externalFetch aliases with source locations', async () => {
|
|
92
|
-
const dir = await makeApp({
|
|
93
|
-
source: `
|
|
94
|
-
import { query } from '@lightdash/query-sdk';
|
|
95
|
-
query('ordrs').dimensions(['missing_field']);
|
|
96
|
-
client.externalFetch('salesforce', { path: '/accounts' });
|
|
97
|
-
`,
|
|
98
|
-
});
|
|
99
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
100
|
-
live: false,
|
|
101
|
-
loadLiveIndex: unusedLiveLoader,
|
|
102
|
-
});
|
|
103
|
-
expect(result.valid).toBe(false);
|
|
104
|
-
expect(result.errors).toEqual(expect.arrayContaining([
|
|
105
|
-
expect.objectContaining({
|
|
106
|
-
code: 'semantic_reference',
|
|
107
|
-
message: "Explore 'ordrs' does not exist — did you mean 'orders'?",
|
|
108
|
-
location: expect.objectContaining({ path: 'src/App.tsx' }),
|
|
109
|
-
}),
|
|
110
|
-
expect.objectContaining({
|
|
111
|
-
code: 'external_connection',
|
|
112
|
-
message: expect.stringContaining('salesforce'),
|
|
113
|
-
location: expect.objectContaining({ path: 'src/App.tsx' }),
|
|
114
|
-
}),
|
|
115
|
-
]));
|
|
116
|
-
});
|
|
117
|
-
it('counts dynamic query call sites as unanalyzed without failing', async () => {
|
|
118
|
-
const dir = await makeApp({
|
|
119
|
-
source: `
|
|
120
|
-
import { query } from '@lightdash/query-sdk';
|
|
121
|
-
function build(explore) {
|
|
122
|
-
return query(explore).dimensions(['status']);
|
|
123
|
-
}
|
|
124
|
-
`,
|
|
125
|
-
});
|
|
126
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
127
|
-
live: false,
|
|
128
|
-
loadLiveIndex: unusedLiveLoader,
|
|
129
|
-
});
|
|
130
|
-
expect(result.valid).toBe(true);
|
|
131
|
-
expect(result.coverage).toMatchObject({
|
|
132
|
-
callSites: 1,
|
|
133
|
-
unresolved: 1,
|
|
134
|
-
unanalyzed: 1,
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
it('does not treat a dynamic externalFetch alias as an error', async () => {
|
|
138
|
-
const dir = await makeApp({
|
|
139
|
-
source: `
|
|
140
|
-
function load(client, alias) {
|
|
141
|
-
return client.externalFetch(alias, { path: '/resource' });
|
|
142
|
-
}
|
|
143
|
-
`,
|
|
144
|
-
semanticLayout: 'none',
|
|
145
|
-
});
|
|
146
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
147
|
-
live: false,
|
|
148
|
-
loadLiveIndex: unusedLiveLoader,
|
|
149
|
-
});
|
|
150
|
-
expect(result.valid).toBe(true);
|
|
151
|
-
expect(result.errors).toEqual([]);
|
|
152
|
-
expect(result.coverage).toMatchObject({
|
|
153
|
-
callSites: 1,
|
|
154
|
-
unresolved: 1,
|
|
155
|
-
unanalyzed: 1,
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
it('uses the live semantic layer and does not require a local snapshot', async () => {
|
|
159
|
-
const dir = await makeApp({ semanticLayout: 'none' });
|
|
160
|
-
const loadLiveIndex = vi.fn().mockResolvedValue(liveIndex);
|
|
161
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
162
|
-
live: true,
|
|
163
|
-
liveProjectUuid: 'configured-project-uuid',
|
|
164
|
-
loadLiveIndex,
|
|
165
|
-
});
|
|
166
|
-
expect(result.valid).toBe(true);
|
|
167
|
-
expect(result.projectUuid).toBe('configured-project-uuid');
|
|
168
|
-
expect(loadLiveIndex).toHaveBeenCalledWith('configured-project-uuid');
|
|
169
|
-
});
|
|
170
|
-
it('explains how to configure a project for live validation', async () => {
|
|
171
|
-
const dir = await makeApp({ semanticLayout: 'none' });
|
|
172
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
173
|
-
live: true,
|
|
174
|
-
loadLiveIndex: unusedLiveLoader,
|
|
175
|
-
});
|
|
176
|
-
expect(result.valid).toBe(false);
|
|
177
|
-
expect(result.projectUuid).toBeNull();
|
|
178
|
-
expect(result.errors).toContainEqual(expect.objectContaining({
|
|
179
|
-
code: 'semantic_layer',
|
|
180
|
-
message: expect.stringContaining('lightdash config set-project'),
|
|
181
|
-
}));
|
|
182
|
-
});
|
|
183
|
-
it('gives an upgrade path instead of crashing on a missing offline snapshot', async () => {
|
|
184
|
-
const dir = await makeApp({ semanticLayout: 'none' });
|
|
185
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
186
|
-
live: false,
|
|
187
|
-
loadLiveIndex: unusedLiveLoader,
|
|
188
|
-
});
|
|
189
|
-
expect(result.valid).toBe(false);
|
|
190
|
-
expect(result.errors).toContainEqual(expect.objectContaining({
|
|
191
|
-
code: 'semantic_layer',
|
|
192
|
-
message: expect.stringContaining('Re-download the app'),
|
|
193
|
-
}));
|
|
194
|
-
});
|
|
195
|
-
it('validates slug and vizSchema from the manifest', async () => {
|
|
196
|
-
const manifest = {
|
|
197
|
-
...defaultManifest,
|
|
198
|
-
slug: '../invalid',
|
|
199
|
-
vizSchema: { fields: 'not-an-array' },
|
|
200
|
-
};
|
|
201
|
-
const dir = await makeApp({ manifest });
|
|
202
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
203
|
-
live: false,
|
|
204
|
-
loadLiveIndex: unusedLiveLoader,
|
|
205
|
-
});
|
|
206
|
-
expect(result.errors).toEqual(expect.arrayContaining([
|
|
207
|
-
expect.objectContaining({
|
|
208
|
-
code: 'manifest',
|
|
209
|
-
message: expect.stringContaining('Invalid slug'),
|
|
210
|
-
}),
|
|
211
|
-
expect.objectContaining({
|
|
212
|
-
code: 'manifest',
|
|
213
|
-
message: expect.stringContaining('Invalid vizSchema'),
|
|
214
|
-
}),
|
|
215
|
-
]));
|
|
216
|
-
});
|
|
217
|
-
it('validates the icon from the manifest', async () => {
|
|
218
|
-
const manifest = {
|
|
219
|
-
...defaultManifest,
|
|
220
|
-
icon: 'not-a-real-icon',
|
|
221
|
-
};
|
|
222
|
-
const dir = await makeApp({ manifest });
|
|
223
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
224
|
-
live: false,
|
|
225
|
-
loadLiveIndex: unusedLiveLoader,
|
|
226
|
-
});
|
|
227
|
-
expect(result.errors).toContainEqual(expect.objectContaining({
|
|
228
|
-
code: 'manifest',
|
|
229
|
-
message: expect.stringContaining('Invalid icon'),
|
|
230
|
-
}));
|
|
231
|
-
});
|
|
232
|
-
it.each([null, 'chart-sankey'])('accepts icon %s', async (icon) => {
|
|
233
|
-
const manifest = { ...defaultManifest, icon };
|
|
234
|
-
const dir = await makeApp({ manifest });
|
|
235
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
236
|
-
live: false,
|
|
237
|
-
loadLiveIndex: unusedLiveLoader,
|
|
238
|
-
});
|
|
239
|
-
expect(result.errors).not.toContainEqual(expect.objectContaining({ code: 'manifest' }));
|
|
240
|
-
});
|
|
241
|
-
it('requires a pnpm lockfile for custom dependencies', async () => {
|
|
242
|
-
const dir = await makeApp({
|
|
243
|
-
packageJson: JSON.stringify({
|
|
244
|
-
dependencies: { 'left-pad': '1.3.0' },
|
|
245
|
-
}),
|
|
246
|
-
});
|
|
247
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
248
|
-
live: false,
|
|
249
|
-
loadLiveIndex: unusedLiveLoader,
|
|
250
|
-
});
|
|
251
|
-
expect(result.errors).toContainEqual(expect.objectContaining({
|
|
252
|
-
code: 'dependencies',
|
|
253
|
-
message: expect.stringContaining('pnpm-lock.yaml'),
|
|
254
|
-
}));
|
|
255
|
-
});
|
|
256
|
-
it('surfaces parser failures as warnings rather than errors', async () => {
|
|
257
|
-
const dir = await makeApp({
|
|
258
|
-
source: 'const broken = ;',
|
|
259
|
-
semanticLayout: 'none',
|
|
260
|
-
});
|
|
261
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
262
|
-
live: false,
|
|
263
|
-
loadLiveIndex: unusedLiveLoader,
|
|
264
|
-
});
|
|
265
|
-
expect(result.valid).toBe(true);
|
|
266
|
-
expect(result.warnings).toContainEqual(expect.objectContaining({ code: 'source_parse' }));
|
|
267
|
-
});
|
|
268
|
-
it('includes Cloud-parity build failures when --build is enabled', async () => {
|
|
269
|
-
const dir = await makeApp();
|
|
270
|
-
const runBuild = vi.fn().mockResolvedValue([
|
|
271
|
-
{
|
|
272
|
-
code: 'build',
|
|
273
|
-
message: 'Vite build failed: broken import',
|
|
274
|
-
location: null,
|
|
275
|
-
},
|
|
276
|
-
]);
|
|
277
|
-
const result = await (0, validate_1.validateLocalDataApp)(dir, {
|
|
278
|
-
build: true,
|
|
279
|
-
live: false,
|
|
280
|
-
loadLiveIndex: unusedLiveLoader,
|
|
281
|
-
runBuild,
|
|
282
|
-
});
|
|
283
|
-
expect(runBuild).toHaveBeenCalledOnce();
|
|
284
|
-
expect(result.valid).toBe(false);
|
|
285
|
-
expect(result.errors).toContainEqual(expect.objectContaining({
|
|
286
|
-
code: 'build',
|
|
287
|
-
message: expect.stringContaining('broken import'),
|
|
288
|
-
}));
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
describe('validateDataAppBuild', () => {
|
|
292
|
-
it('builds source in an isolated vendored scaffold with bare Vite', async () => {
|
|
293
|
-
const dir = await makeApp();
|
|
294
|
-
await fs_1.promises.mkdir(path.join(dir, 'node_modules'));
|
|
295
|
-
const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
296
|
-
const runCommand = vi.fn(async (command) => {
|
|
297
|
-
expect(command).toMatchObject({
|
|
298
|
-
command: 'vite',
|
|
299
|
-
args: ['build'],
|
|
300
|
-
preferLocal: true,
|
|
301
|
-
timeoutMs: 60_000,
|
|
302
|
-
});
|
|
303
|
-
expect(command.args).not.toContain('tsc');
|
|
304
|
-
await expect(fs_1.promises.readFile(path.join(command.cwd, 'vite.config.js'), 'utf-8')).resolves.toContain('defineConfig');
|
|
305
|
-
await expect(fs_1.promises.readFile(path.join(command.cwd, 'src', 'App.tsx'), 'utf-8')).resolves.toContain("query('orders')");
|
|
306
|
-
expect((await fs_1.promises.lstat(path.join(command.cwd, 'node_modules'))).isSymbolicLink()).toBe(true);
|
|
307
|
-
});
|
|
308
|
-
const issues = await (0, validate_1.validateDataAppBuild)({
|
|
309
|
-
appDir: dir,
|
|
310
|
-
bundle,
|
|
311
|
-
runCommand,
|
|
312
|
-
});
|
|
313
|
-
expect(issues).toEqual([]);
|
|
314
|
-
expect(runCommand).toHaveBeenCalledOnce();
|
|
315
|
-
});
|
|
316
|
-
it('restores custom dependencies with a frozen lockfile before Vite', async () => {
|
|
317
|
-
const dir = await makeApp({
|
|
318
|
-
packageJson: JSON.stringify({
|
|
319
|
-
name: 'custom-app',
|
|
320
|
-
version: '1.0.0',
|
|
321
|
-
dependencies: { 'left-pad': '1.3.0' },
|
|
322
|
-
devDependencies: { malicious: '1.0.0' },
|
|
323
|
-
scripts: { build: 'tsc --noEmit' },
|
|
324
|
-
}),
|
|
325
|
-
lockfile: [
|
|
326
|
-
"lockfileVersion: '9.0'",
|
|
327
|
-
'packages:',
|
|
328
|
-
' left-pad@1.3.0: {}',
|
|
329
|
-
].join('\n'),
|
|
330
|
-
});
|
|
331
|
-
const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
332
|
-
const commands = [];
|
|
333
|
-
const runCommand = vi.fn(async (command) => {
|
|
334
|
-
commands.push(command);
|
|
335
|
-
if (command.command === 'pnpm') {
|
|
336
|
-
const packageJson = JSON.parse(await fs_1.promises.readFile(path.join(command.cwd, 'package.json'), 'utf-8'));
|
|
337
|
-
expect(packageJson.dependencies).toEqual({
|
|
338
|
-
'left-pad': '1.3.0',
|
|
339
|
-
});
|
|
340
|
-
expect(packageJson.scripts).toEqual(expect.objectContaining({ build: 'vite build' }));
|
|
341
|
-
expect(packageJson.devDependencies).not.toHaveProperty('malicious');
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
const issues = await (0, validate_1.validateDataAppBuild)({
|
|
345
|
-
appDir: dir,
|
|
346
|
-
bundle,
|
|
347
|
-
runCommand,
|
|
348
|
-
});
|
|
349
|
-
expect(issues).toEqual([]);
|
|
350
|
-
expect(commands).toEqual([
|
|
351
|
-
expect.objectContaining({
|
|
352
|
-
command: 'pnpm',
|
|
353
|
-
args: ['install', '--frozen-lockfile', '--ignore-scripts'],
|
|
354
|
-
env: { CI: 'true' },
|
|
355
|
-
timeoutMs: 120_000,
|
|
356
|
-
}),
|
|
357
|
-
expect.objectContaining({
|
|
358
|
-
command: 'vite',
|
|
359
|
-
args: ['build'],
|
|
360
|
-
preferLocal: true,
|
|
361
|
-
}),
|
|
362
|
-
]);
|
|
363
|
-
});
|
|
364
|
-
it('reports Vite output as a build validation error', async () => {
|
|
365
|
-
const dir = await makeApp();
|
|
366
|
-
await fs_1.promises.mkdir(path.join(dir, 'node_modules'));
|
|
367
|
-
const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
368
|
-
const viteError = Object.assign(new Error('Command failed'), {
|
|
369
|
-
stderr: 'src/App.tsx:7: Could not resolve "missing-package"',
|
|
370
|
-
stdout: 'vite v8 building for production',
|
|
371
|
-
});
|
|
372
|
-
const issues = await (0, validate_1.validateDataAppBuild)({
|
|
373
|
-
appDir: dir,
|
|
374
|
-
bundle,
|
|
375
|
-
runCommand: vi.fn().mockRejectedValue(viteError),
|
|
376
|
-
});
|
|
377
|
-
expect(issues).toEqual([
|
|
378
|
-
expect.objectContaining({
|
|
379
|
-
code: 'build',
|
|
380
|
-
message: expect.stringContaining('Could not resolve "missing-package"'),
|
|
381
|
-
}),
|
|
382
|
-
]);
|
|
383
|
-
});
|
|
384
|
-
it('handles non-object command failures without masking them', async () => {
|
|
385
|
-
const dir = await makeApp();
|
|
386
|
-
await fs_1.promises.mkdir(path.join(dir, 'node_modules'));
|
|
387
|
-
const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
388
|
-
const issues = await (0, validate_1.validateDataAppBuild)({
|
|
389
|
-
appDir: dir,
|
|
390
|
-
bundle,
|
|
391
|
-
runCommand: vi.fn().mockRejectedValue(null),
|
|
392
|
-
});
|
|
393
|
-
expect(issues).toEqual([
|
|
394
|
-
expect.objectContaining({
|
|
395
|
-
code: 'build',
|
|
396
|
-
message: expect.stringContaining('Unknown object error'),
|
|
397
|
-
}),
|
|
398
|
-
]);
|
|
399
|
-
});
|
|
400
|
-
it('requires local template dependencies without installing them', async () => {
|
|
401
|
-
const dir = await makeApp();
|
|
402
|
-
const bundle = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
403
|
-
const runCommand = vi.fn();
|
|
404
|
-
const issues = await (0, validate_1.validateDataAppBuild)({
|
|
405
|
-
appDir: dir,
|
|
406
|
-
bundle,
|
|
407
|
-
runCommand,
|
|
408
|
-
});
|
|
409
|
-
expect(issues).toEqual([
|
|
410
|
-
expect.objectContaining({
|
|
411
|
-
code: 'dependencies',
|
|
412
|
-
message: expect.stringContaining("Run 'npm install'"),
|
|
413
|
-
}),
|
|
414
|
-
]);
|
|
415
|
-
expect(runCommand).not.toHaveBeenCalled();
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
describe('renderAppsValidationHuman', () => {
|
|
419
|
-
const report = (0, validate_1.buildAppsValidationReport)([
|
|
420
|
-
{
|
|
421
|
-
path: '/tmp/orders-app',
|
|
422
|
-
name: 'Orders app',
|
|
423
|
-
projectUuid: 'project-uuid',
|
|
424
|
-
template: null,
|
|
425
|
-
valid: true,
|
|
426
|
-
errors: [],
|
|
427
|
-
warnings: [],
|
|
428
|
-
coverage: {
|
|
429
|
-
callSites: 14,
|
|
430
|
-
fullyResolved: 11,
|
|
431
|
-
partiallyResolved: 2,
|
|
432
|
-
unresolved: 1,
|
|
433
|
-
unanalyzed: 3,
|
|
434
|
-
},
|
|
435
|
-
unanalyzedReferences: [
|
|
436
|
-
{
|
|
437
|
-
kind: 'query',
|
|
438
|
-
unresolved: ['explore', 'dimensions'],
|
|
439
|
-
location: {
|
|
440
|
-
path: 'src/DynamicQuery.tsx',
|
|
441
|
-
line: 8,
|
|
442
|
-
column: 17,
|
|
443
|
-
},
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
kind: 'globalFilter',
|
|
447
|
-
unresolved: ['field'],
|
|
448
|
-
location: {
|
|
449
|
-
path: 'src/ResultsTable.tsx',
|
|
450
|
-
line: 42,
|
|
451
|
-
column: 21,
|
|
452
|
-
},
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
kind: 'externalFetch',
|
|
456
|
-
unresolved: ['alias'],
|
|
457
|
-
location: {
|
|
458
|
-
path: 'src/ExternalData.tsx',
|
|
459
|
-
line: 12,
|
|
460
|
-
column: 9,
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
],
|
|
464
|
-
},
|
|
465
|
-
], false, true);
|
|
466
|
-
it('makes partial green coverage explicit', () => {
|
|
467
|
-
const output = (0, validate_1.renderAppsValidationHuman)(report);
|
|
468
|
-
expect(output).toContain("3 of 14 data-reference call site(s) couldn't be fully analyzed");
|
|
469
|
-
expect(output).toContain('unresolved values were skipped');
|
|
470
|
-
expect(output).toContain('Validation passed');
|
|
471
|
-
expect(output).toContain('Offline snapshots may be stale');
|
|
472
|
-
expect(output).toContain('running Vite production builds');
|
|
473
|
-
expect(output).not.toContain('Static analysis gaps');
|
|
474
|
-
});
|
|
475
|
-
it('lists unresolved call sites and parts in verbose output', () => {
|
|
476
|
-
const output = (0, validate_1.renderAppsValidationHuman)(report, true);
|
|
477
|
-
expect(output).toContain('Static analysis gaps:');
|
|
478
|
-
expect(output).toContain('src/DynamicQuery.tsx:8:17 — query (unresolved: explore, dimensions)');
|
|
479
|
-
expect(output).toContain('src/ResultsTable.tsx:42:21 — global filter (unresolved: field)');
|
|
480
|
-
});
|
|
481
|
-
it('keeps verbose-only reference details out of JSON output', () => {
|
|
482
|
-
const output = (0, validate_1.renderAppsValidationJson)(report);
|
|
483
|
-
expect(JSON.parse(output)).not.toHaveProperty('apps.0.unanalyzedReferences');
|
|
484
|
-
});
|
|
485
|
-
const buildResult = (template, overrides = {}) => ({
|
|
486
|
-
path: '/tmp/bundle',
|
|
487
|
-
name: 'Bundle',
|
|
488
|
-
projectUuid: null,
|
|
489
|
-
template,
|
|
490
|
-
valid: true,
|
|
491
|
-
errors: [],
|
|
492
|
-
warnings: [],
|
|
493
|
-
coverage: {
|
|
494
|
-
callSites: 0,
|
|
495
|
-
fullyResolved: 0,
|
|
496
|
-
partiallyResolved: 0,
|
|
497
|
-
unresolved: 0,
|
|
498
|
-
unanalyzed: 0,
|
|
499
|
-
},
|
|
500
|
-
unanalyzedReferences: [],
|
|
501
|
-
...overrides,
|
|
502
|
-
});
|
|
503
|
-
it('describes an all-chart-type run as custom chart types', () => {
|
|
504
|
-
const vizReport = (0, validate_1.buildAppsValidationReport)([buildResult(common_1.DATA_APP_VIZ_TEMPLATE)], false);
|
|
505
|
-
const output = (0, validate_1.renderAppsValidationHuman)(vizReport);
|
|
506
|
-
expect(output).toContain('Validating 1 custom chart type(s)');
|
|
507
|
-
expect(output).toContain('Validation passed for 1 custom chart type(s)');
|
|
508
|
-
expect(output).not.toContain('data app(s)');
|
|
509
|
-
expect(vizReport.summary.chartTypes).toBe(1);
|
|
510
|
-
expect(vizReport.summary.dataApps).toBe(0);
|
|
511
|
-
});
|
|
512
|
-
it('describes a mixed run with both nouns', () => {
|
|
513
|
-
const mixedReport = (0, validate_1.buildAppsValidationReport)([
|
|
514
|
-
buildResult(common_1.DATA_APP_VIZ_TEMPLATE, {
|
|
515
|
-
valid: false,
|
|
516
|
-
errors: [
|
|
517
|
-
{ code: 'manifest', message: 'bad', location: null },
|
|
518
|
-
],
|
|
519
|
-
}),
|
|
520
|
-
buildResult(null),
|
|
521
|
-
], false);
|
|
522
|
-
const output = (0, validate_1.renderAppsValidationHuman)(mixedReport);
|
|
523
|
-
expect(output).toContain('Validating 1 data app(s) and 1 custom chart type(s)');
|
|
524
|
-
expect(output).toContain('Validation failed with 1 error(s) across 1 data app(s) and 1 custom chart type(s).');
|
|
525
|
-
});
|
|
526
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compile.test.d.ts","sourceRoot":"","sources":["../../src/handlers/compile.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const common_1 = require("@lightdash/common");
|
|
5
|
-
const promises_1 = tslib_1.__importDefault(require("fs/promises"));
|
|
6
|
-
const os = tslib_1.__importStar(require("os"));
|
|
7
|
-
const path = tslib_1.__importStar(require("path"));
|
|
8
|
-
const lightdashLoader = tslib_1.__importStar(require("../lightdash/loader"));
|
|
9
|
-
const compile_1 = require("./compile");
|
|
10
|
-
vi.mock('execa');
|
|
11
|
-
vi.mock('../analytics/analytics');
|
|
12
|
-
vi.mock('../config', () => ({
|
|
13
|
-
getConfig: vi.fn().mockResolvedValue({ user: null, context: null }),
|
|
14
|
-
}));
|
|
15
|
-
const modelWithBrokenMetric = {
|
|
16
|
-
type: 'model',
|
|
17
|
-
name: 'test_model',
|
|
18
|
-
description: 'Test model',
|
|
19
|
-
sql_from: 'SELECT * FROM test_table',
|
|
20
|
-
dimensions: [
|
|
21
|
-
{
|
|
22
|
-
name: 'id',
|
|
23
|
-
description: 'ID column',
|
|
24
|
-
type: common_1.DimensionType.NUMBER,
|
|
25
|
-
sql: 'id',
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
metrics: {
|
|
29
|
-
broken_metric: {
|
|
30
|
-
type: common_1.MetricType.SUM,
|
|
31
|
-
sql: `\${missing_dimension}`,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
const getCompileOptions = (projectDir) => ({
|
|
36
|
-
projectDir,
|
|
37
|
-
profilesDir: '',
|
|
38
|
-
target: undefined,
|
|
39
|
-
profile: undefined,
|
|
40
|
-
vars: undefined,
|
|
41
|
-
verbose: false,
|
|
42
|
-
startOfWeek: 0,
|
|
43
|
-
skipWarehouseCatalog: true,
|
|
44
|
-
skipDbtCompile: true,
|
|
45
|
-
useDbtList: false,
|
|
46
|
-
select: undefined,
|
|
47
|
-
models: undefined,
|
|
48
|
-
threads: undefined,
|
|
49
|
-
noVersionCheck: false,
|
|
50
|
-
exclude: undefined,
|
|
51
|
-
selector: undefined,
|
|
52
|
-
state: undefined,
|
|
53
|
-
fullRefresh: false,
|
|
54
|
-
defer: false,
|
|
55
|
-
targetPath: undefined,
|
|
56
|
-
favorState: false,
|
|
57
|
-
combineManifest: undefined,
|
|
58
|
-
warehouseCredentials: false,
|
|
59
|
-
disableTimestampConversion: false,
|
|
60
|
-
});
|
|
61
|
-
describe('compile', () => {
|
|
62
|
-
let tempDir;
|
|
63
|
-
beforeEach(async () => {
|
|
64
|
-
vi.clearAllMocks();
|
|
65
|
-
tempDir = await promises_1.default.mkdtemp(path.join(os.tmpdir(), 'lightdash-compile-test-'));
|
|
66
|
-
// Create minimal lightdash.config.yml
|
|
67
|
-
await promises_1.default.writeFile(path.join(tempDir, 'lightdash.config.yml'), `
|
|
68
|
-
warehouse:
|
|
69
|
-
type: postgres
|
|
70
|
-
`);
|
|
71
|
-
// Create lightdash/models directory and a test model
|
|
72
|
-
const modelsDir = path.join(tempDir, 'lightdash', 'models');
|
|
73
|
-
await promises_1.default.mkdir(modelsDir, { recursive: true });
|
|
74
|
-
await promises_1.default.writeFile(path.join(modelsDir, 'test_model.yml'), `
|
|
75
|
-
version: 1
|
|
76
|
-
type: model
|
|
77
|
-
name: test_model
|
|
78
|
-
description: Test model
|
|
79
|
-
sql_from: "SELECT * FROM test_table"
|
|
80
|
-
dimensions:
|
|
81
|
-
- name: id
|
|
82
|
-
description: ID column
|
|
83
|
-
type: number
|
|
84
|
-
sql: id
|
|
85
|
-
`);
|
|
86
|
-
});
|
|
87
|
-
afterEach(async () => {
|
|
88
|
-
vi.restoreAllMocks();
|
|
89
|
-
await promises_1.default.rm(tempDir, { recursive: true, force: true });
|
|
90
|
-
});
|
|
91
|
-
test('should compile Lightdash YAML project without dbt installed', async () => {
|
|
92
|
-
// Mock analytics to prevent actual tracking
|
|
93
|
-
const { LightdashAnalytics } = await Promise.resolve().then(() => tslib_1.__importStar(require('../analytics/analytics')));
|
|
94
|
-
LightdashAnalytics.track.mockResolvedValue(undefined);
|
|
95
|
-
const result = await (0, compile_1.compile)(getCompileOptions(tempDir));
|
|
96
|
-
// Should succeed even though dbt is not installed
|
|
97
|
-
expect(result).toBeDefined();
|
|
98
|
-
expect(result.length).toBeGreaterThan(0);
|
|
99
|
-
// Analytics should be called even though dbt is not found
|
|
100
|
-
const trackCalls = LightdashAnalytics.track.mock.calls;
|
|
101
|
-
expect(LightdashAnalytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
102
|
-
event: 'compile.started',
|
|
103
|
-
properties: expect.objectContaining({
|
|
104
|
-
dbtVersion: undefined,
|
|
105
|
-
}),
|
|
106
|
-
}));
|
|
107
|
-
expect(LightdashAnalytics.track).toHaveBeenCalledWith(expect.objectContaining({
|
|
108
|
-
event: 'compile.completed',
|
|
109
|
-
}));
|
|
110
|
-
// Verify compile.started was called with undefined dbtVersion
|
|
111
|
-
const startedCall = trackCalls.find((call) => call[0].event === 'compile.started');
|
|
112
|
-
if (startedCall === undefined) {
|
|
113
|
-
throw new Error('Expected compile.started analytics event');
|
|
114
|
-
}
|
|
115
|
-
expect(startedCall[0].properties.dbtVersion).toBeUndefined();
|
|
116
|
-
});
|
|
117
|
-
test('should allow partial compilation by default and allow it to be disabled explicitly', async () => {
|
|
118
|
-
vi.spyOn(lightdashLoader, 'loadLightdashModels').mockResolvedValue([
|
|
119
|
-
{ ...modelWithBrokenMetric, sourcePath: 'models/test_model.yml' },
|
|
120
|
-
]);
|
|
121
|
-
const errorOutput = [];
|
|
122
|
-
vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
123
|
-
errorOutput.push(args.map(String).join(' '));
|
|
124
|
-
});
|
|
125
|
-
const partialResult = await (0, compile_1.compile)(getCompileOptions(tempDir));
|
|
126
|
-
expect(partialResult.filter(common_1.isExploreError)).toHaveLength(0);
|
|
127
|
-
expect(partialResult.flatMap((explore) => (0, common_1.isExploreError)(explore) ? [] : (explore.warnings ?? []))).toEqual(expect.arrayContaining([
|
|
128
|
-
expect.objectContaining({
|
|
129
|
-
message: expect.stringContaining('broken_metric'),
|
|
130
|
-
}),
|
|
131
|
-
]));
|
|
132
|
-
expect(errorOutput).toEqual(expect.arrayContaining([
|
|
133
|
-
expect.stringContaining('- PARTIAL_SUCCESS> test_model'),
|
|
134
|
-
expect.stringContaining('Compiled 1 explores, SUCCESS=0 PARTIAL_SUCCESS=1 ERRORS=0'),
|
|
135
|
-
]));
|
|
136
|
-
errorOutput.length = 0;
|
|
137
|
-
const strictResult = await (0, compile_1.compile)({
|
|
138
|
-
...getCompileOptions(tempDir),
|
|
139
|
-
partialCompilation: false,
|
|
140
|
-
});
|
|
141
|
-
expect(strictResult.filter(common_1.isExploreError)).toHaveLength(1);
|
|
142
|
-
expect(errorOutput).toEqual(expect.arrayContaining([
|
|
143
|
-
expect.stringContaining('- ERROR> test_model'),
|
|
144
|
-
expect.stringContaining('Compiled 1 explores, SUCCESS=0 ERRORS=1'),
|
|
145
|
-
]));
|
|
146
|
-
});
|
|
147
|
-
test('rejects malformed native YAML instead of compiling only the valid models', async () => {
|
|
148
|
-
await promises_1.default.writeFile(path.join(tempDir, 'lightdash/models/broken.yml'), 'type: model\ndimensions: [');
|
|
149
|
-
await expect((0, compile_1.compile)(getCompileOptions(tempDir))).rejects.toThrow(/broken.yml/);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compileProject.test.d.ts","sourceRoot":"","sources":["../../src/handlers/compileProject.test.ts"],"names":[],"mappings":""}
|