@lightdash/cli 2.228.1 → 2.229.0
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,639 +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 = tslib_1.__importStar(require("fs"));
|
|
6
|
-
const os = tslib_1.__importStar(require("os"));
|
|
7
|
-
const path = tslib_1.__importStar(require("path"));
|
|
8
|
-
const appsDownload_1 = require("./appsDownload");
|
|
9
|
-
describe('getDataAppReference', () => {
|
|
10
|
-
const appUuid = 'd3afc44c-6f0f-4d9f-a267-fb739efa31dd';
|
|
11
|
-
const projectUuid = 'd4c8dfd2-98c0-4eb2-8395-d924aee62611';
|
|
12
|
-
it('keeps a bare app UUID unchanged', () => {
|
|
13
|
-
expect((0, appsDownload_1.getDataAppReference)(appUuid)).toBe(appUuid);
|
|
14
|
-
});
|
|
15
|
-
it('keeps a bare slug unchanged', () => {
|
|
16
|
-
expect((0, appsDownload_1.getDataAppReference)('my-app-slug')).toBe('my-app-slug');
|
|
17
|
-
});
|
|
18
|
-
it.each([
|
|
19
|
-
`https://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}`,
|
|
20
|
-
`https://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}/view`,
|
|
21
|
-
`https://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}/versions/2/view?state=filters#preview`,
|
|
22
|
-
`https://app.lightdash.cloud/embed/${projectUuid}/app/${appUuid}`,
|
|
23
|
-
])('extracts the app UUID from %s', (url) => {
|
|
24
|
-
expect((0, appsDownload_1.getDataAppReference)(url)).toBe(appUuid);
|
|
25
|
-
});
|
|
26
|
-
it.each([
|
|
27
|
-
'my-app',
|
|
28
|
-
`https://app.lightdash.cloud/${appUuid}`,
|
|
29
|
-
`https://app.lightdash.cloud/projects/${projectUuid}/apps/generate`,
|
|
30
|
-
`ftp://app.lightdash.cloud/projects/${projectUuid}/apps/${appUuid}`,
|
|
31
|
-
])('leaves unsupported references unchanged: %s', (reference) => {
|
|
32
|
-
expect((0, appsDownload_1.getDataAppReference)(reference)).toBe(reference);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe('uploadFilterMatches', () => {
|
|
36
|
-
const makeManifest = (overrides = {}) => ({
|
|
37
|
-
codeVersion: 1,
|
|
38
|
-
appUuid: 'app-uuid-1',
|
|
39
|
-
projectUuid: 'proj-uuid-1',
|
|
40
|
-
version: 3,
|
|
41
|
-
name: 'My App',
|
|
42
|
-
description: '',
|
|
43
|
-
template: null,
|
|
44
|
-
downloadedAt: '2026-06-25T00:00:00.000Z',
|
|
45
|
-
...overrides,
|
|
46
|
-
});
|
|
47
|
-
it('matches everything when the filter is null', () => {
|
|
48
|
-
expect((0, appsDownload_1.uploadFilterMatches)(null, makeManifest())).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
it('matches on manifest.appUuid', () => {
|
|
51
|
-
const filter = new Set(['app-uuid-1']);
|
|
52
|
-
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest())).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
it('matches on manifest.slug', () => {
|
|
55
|
-
const filter = new Set(['sales-app']);
|
|
56
|
-
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest({ slug: 'sales-app' }))).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
it('does not match when neither uuid nor slug is in the filter', () => {
|
|
59
|
-
const filter = new Set(['other-uuid']);
|
|
60
|
-
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest({ slug: 'sales-app' }))).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
it('matches only by uuid when the manifest has no slug', () => {
|
|
63
|
-
const filter = new Set(['app-uuid-1']);
|
|
64
|
-
expect((0, appsDownload_1.uploadFilterMatches)(filter, makeManifest({ slug: undefined }))).toBe(true);
|
|
65
|
-
expect((0, appsDownload_1.uploadFilterMatches)(new Set(['sales-app']), makeManifest({ slug: undefined }))).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
it('matches only by slug for uuid-free manifests (slug-aware servers)', () => {
|
|
68
|
-
const manifest = makeManifest({
|
|
69
|
-
appUuid: undefined,
|
|
70
|
-
slug: 'sales-app',
|
|
71
|
-
});
|
|
72
|
-
expect((0, appsDownload_1.uploadFilterMatches)(new Set(['sales-app']), manifest)).toBe(true);
|
|
73
|
-
expect((0, appsDownload_1.uploadFilterMatches)(new Set(['app-uuid-1']), manifest)).toBe(false);
|
|
74
|
-
expect((0, appsDownload_1.uploadFilterMatches)(null, manifest)).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe('getDataAppUploadFilter', () => {
|
|
78
|
-
const appUuid = 'd3afc44c-6f0f-4d9f-a267-fb739efa31dd';
|
|
79
|
-
it('matches app manifests by UUID when a URL is passed', () => {
|
|
80
|
-
expect((0, appsDownload_1.getDataAppUploadFilter)([
|
|
81
|
-
`https://app.lightdash.cloud/projects/project-uuid/apps/${appUuid}/view`,
|
|
82
|
-
], false)).toEqual(new Set([appUuid]));
|
|
83
|
-
});
|
|
84
|
-
it('does not filter app folders when --include-apps is passed', () => {
|
|
85
|
-
expect((0, appsDownload_1.getDataAppUploadFilter)([appUuid], true)).toBeNull();
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
const makeDownload = () => ({
|
|
89
|
-
manifest: {
|
|
90
|
-
codeVersion: 1,
|
|
91
|
-
appUuid: 'app-uuid-1',
|
|
92
|
-
projectUuid: 'proj-uuid-1',
|
|
93
|
-
version: 3,
|
|
94
|
-
name: 'My App',
|
|
95
|
-
description: '',
|
|
96
|
-
template: null,
|
|
97
|
-
downloadedAt: '2026-06-25T00:00:00.000Z',
|
|
98
|
-
},
|
|
99
|
-
files: [],
|
|
100
|
-
context: {
|
|
101
|
-
semanticLayer: {
|
|
102
|
-
path: '.lightdash/context/semantic-layer.yml',
|
|
103
|
-
contentBase64: '',
|
|
104
|
-
},
|
|
105
|
-
parameters: null,
|
|
106
|
-
promptHistory: {
|
|
107
|
-
path: '.lightdash/context/prompt-history.md',
|
|
108
|
-
contentBase64: '',
|
|
109
|
-
},
|
|
110
|
-
theme: { instructions: null, assets: [], skippedAssetCount: 0 },
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
describe('selectAppsToDownload', () => {
|
|
114
|
-
it('returns explicit refs without listing when refs are passed', () => {
|
|
115
|
-
expect((0, appsDownload_1.selectAppsToDownload)({ apps: ['uuid-a', 'uuid-b'] })).toEqual({
|
|
116
|
-
mode: 'explicit',
|
|
117
|
-
appRefs: ['uuid-a', 'uuid-b'],
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
it('lists all apps when --include-apps is passed', () => {
|
|
121
|
-
expect((0, appsDownload_1.selectAppsToDownload)({ includeApps: true })).toEqual({
|
|
122
|
-
mode: 'list-all',
|
|
123
|
-
extraAppRefs: [],
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
it('combines --include-apps with explicitly passed refs', () => {
|
|
127
|
-
expect((0, appsDownload_1.selectAppsToDownload)({ apps: ['uuid-a'], includeApps: true })).toEqual({ mode: 'list-all', extraAppRefs: ['uuid-a'] });
|
|
128
|
-
});
|
|
129
|
-
it('normalizes explicit app URLs without listing apps', () => {
|
|
130
|
-
expect((0, appsDownload_1.selectAppsToDownload)({
|
|
131
|
-
apps: [
|
|
132
|
-
'https://app.lightdash.cloud/projects/project-uuid/apps/d3afc44c-6f0f-4d9f-a267-fb739efa31dd/view',
|
|
133
|
-
],
|
|
134
|
-
})).toEqual({
|
|
135
|
-
mode: 'explicit',
|
|
136
|
-
appRefs: ['d3afc44c-6f0f-4d9f-a267-fb739efa31dd'],
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
it('skips apps when neither flag is passed', () => {
|
|
140
|
-
expect((0, appsDownload_1.selectAppsToDownload)({})).toEqual({ mode: 'none' });
|
|
141
|
-
expect((0, appsDownload_1.selectAppsToDownload)({ apps: [] })).toEqual({ mode: 'none' });
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
describe('capListedApps', () => {
|
|
145
|
-
it('keeps all listed apps when under the limit', () => {
|
|
146
|
-
expect((0, appsDownload_1.capListedApps)(['a', 'b', 'c'], 5)).toEqual({
|
|
147
|
-
appUuids: ['a', 'b', 'c'],
|
|
148
|
-
truncatedCount: 0,
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
it('caps listed apps at the given limit and reports the truncated count', () => {
|
|
152
|
-
const listed = Array.from({ length: 12 }, (_, i) => `uuid-${i}`);
|
|
153
|
-
const result = (0, appsDownload_1.capListedApps)(listed, 10);
|
|
154
|
-
expect(result.appUuids).toHaveLength(10);
|
|
155
|
-
expect(result.appUuids).toEqual(listed.slice(0, 10));
|
|
156
|
-
expect(result.truncatedCount).toBe(2);
|
|
157
|
-
});
|
|
158
|
-
it('reports no truncation when exactly at the limit', () => {
|
|
159
|
-
expect((0, appsDownload_1.capListedApps)(['a', 'b'], 2)).toEqual({
|
|
160
|
-
appUuids: ['a', 'b'],
|
|
161
|
-
truncatedCount: 0,
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
describe('computeLinkedAppSlugs', () => {
|
|
166
|
-
it('returns dashboard-referenced slugs unchanged when apps are not opted in at all', () => {
|
|
167
|
-
expect((0, appsDownload_1.computeLinkedAppSlugs)({
|
|
168
|
-
appSlugs: ['revenue-explorer', 'sales-dash'],
|
|
169
|
-
explicitRefs: new Set(),
|
|
170
|
-
includeApps: false,
|
|
171
|
-
cappedAppSlugs: new Set(),
|
|
172
|
-
})).toEqual(['revenue-explorer', 'sales-dash']);
|
|
173
|
-
});
|
|
174
|
-
it('excludes slugs already covered by an explicit --apps ref', () => {
|
|
175
|
-
expect((0, appsDownload_1.computeLinkedAppSlugs)({
|
|
176
|
-
appSlugs: ['revenue-explorer', 'sales-dash'],
|
|
177
|
-
explicitRefs: new Set(['revenue-explorer']),
|
|
178
|
-
includeApps: false,
|
|
179
|
-
cappedAppSlugs: new Set(),
|
|
180
|
-
})).toEqual(['sales-dash']);
|
|
181
|
-
});
|
|
182
|
-
it('excludes everything already inside an untruncated --include-apps listing', () => {
|
|
183
|
-
expect((0, appsDownload_1.computeLinkedAppSlugs)({
|
|
184
|
-
appSlugs: ['revenue-explorer', 'sales-dash'],
|
|
185
|
-
explicitRefs: new Set(),
|
|
186
|
-
includeApps: true,
|
|
187
|
-
cappedAppSlugs: new Set(['revenue-explorer', 'sales-dash']),
|
|
188
|
-
})).toEqual([]);
|
|
189
|
-
});
|
|
190
|
-
// PROD-9089 regression: --include-apps caps its listing at --apps-limit,
|
|
191
|
-
// so a dashboard's app can fall outside the capped set. It must still be
|
|
192
|
-
// downloaded via the linked step rather than silently dropped.
|
|
193
|
-
it('keeps slugs that fell outside a truncated --include-apps listing', () => {
|
|
194
|
-
expect((0, appsDownload_1.computeLinkedAppSlugs)({
|
|
195
|
-
appSlugs: ['revenue-explorer', 'sales-dash'],
|
|
196
|
-
explicitRefs: new Set(),
|
|
197
|
-
includeApps: true,
|
|
198
|
-
cappedAppSlugs: new Set(['sales-dash']),
|
|
199
|
-
})).toEqual(['revenue-explorer']);
|
|
200
|
-
});
|
|
201
|
-
it('an explicit ref wins over include-apps capping (never double counted)', () => {
|
|
202
|
-
expect((0, appsDownload_1.computeLinkedAppSlugs)({
|
|
203
|
-
appSlugs: ['revenue-explorer'],
|
|
204
|
-
explicitRefs: new Set(['revenue-explorer']),
|
|
205
|
-
includeApps: true,
|
|
206
|
-
cappedAppSlugs: new Set(),
|
|
207
|
-
})).toEqual([]);
|
|
208
|
-
});
|
|
209
|
-
it('returns an empty array when no dashboards reference any app', () => {
|
|
210
|
-
expect((0, appsDownload_1.computeLinkedAppSlugs)({
|
|
211
|
-
appSlugs: [],
|
|
212
|
-
explicitRefs: new Set(['other-app']),
|
|
213
|
-
includeApps: true,
|
|
214
|
-
cappedAppSlugs: new Set(),
|
|
215
|
-
})).toEqual([]);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
describe('resolveAppsLimit', () => {
|
|
219
|
-
it('defaults to DEFAULT_APPS_LIMIT when the flag is not passed', () => {
|
|
220
|
-
expect(appsDownload_1.DEFAULT_APPS_LIMIT).toBe(50);
|
|
221
|
-
expect((0, appsDownload_1.resolveAppsLimit)(undefined, true)).toEqual({
|
|
222
|
-
limit: appsDownload_1.DEFAULT_APPS_LIMIT,
|
|
223
|
-
noEffectWarning: null,
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
it('does not warn when the flag is not passed, even without --include-apps', () => {
|
|
227
|
-
expect((0, appsDownload_1.resolveAppsLimit)(undefined, false)).toEqual({
|
|
228
|
-
limit: appsDownload_1.DEFAULT_APPS_LIMIT,
|
|
229
|
-
noEffectWarning: null,
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
it('parses an explicit limit', () => {
|
|
233
|
-
expect((0, appsDownload_1.resolveAppsLimit)('200', true)).toEqual({
|
|
234
|
-
limit: 200,
|
|
235
|
-
noEffectWarning: null,
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
it('warns (but succeeds) when passed without --include-apps', () => {
|
|
239
|
-
const result = (0, appsDownload_1.resolveAppsLimit)('5', false);
|
|
240
|
-
expect(result.limit).toBe(5);
|
|
241
|
-
expect(result.noEffectWarning).toContain('--include-apps');
|
|
242
|
-
});
|
|
243
|
-
it.each(['0', '-5', 'abc', '1.5', ''])('rejects %p as a ParameterError', (raw) => {
|
|
244
|
-
expect(() => (0, appsDownload_1.resolveAppsLimit)(raw, true)).toThrow(common_1.ParameterError);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
describe('shouldFallBackToSpaceScopedListing', () => {
|
|
248
|
-
const makeError = (statusCode) => new common_1.LightdashError({
|
|
249
|
-
message: 'x',
|
|
250
|
-
name: 'TestError',
|
|
251
|
-
statusCode,
|
|
252
|
-
data: {},
|
|
253
|
-
});
|
|
254
|
-
it('falls back when the listing endpoint 404s (older server)', () => {
|
|
255
|
-
expect((0, appsDownload_1.shouldFallBackToSpaceScopedListing)(makeError(404))).toBe(true);
|
|
256
|
-
});
|
|
257
|
-
it('propagates 403 (data apps disabled) instead of falling back', () => {
|
|
258
|
-
expect((0, appsDownload_1.shouldFallBackToSpaceScopedListing)(makeError(403))).toBe(false);
|
|
259
|
-
});
|
|
260
|
-
it('propagates non-Lightdash errors', () => {
|
|
261
|
-
expect((0, appsDownload_1.shouldFallBackToSpaceScopedListing)(new Error('boom'))).toBe(false);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
describe('ensureDownloadedAppContext', () => {
|
|
265
|
-
it('returns the code unchanged when context is present', () => {
|
|
266
|
-
const code = makeDownload();
|
|
267
|
-
expect((0, appsDownload_1.ensureDownloadedAppContext)('app-uuid-1', code)).toBe(code);
|
|
268
|
-
});
|
|
269
|
-
it('throws an actionable error when the server response has no context (old server)', () => {
|
|
270
|
-
const code = makeDownload();
|
|
271
|
-
const { context, ...withoutContext } = code;
|
|
272
|
-
expect(() => (0, appsDownload_1.ensureDownloadedAppContext)('app-uuid-1', withoutContext)).toThrow(/does not support app context downloads/i);
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
describe('preSlugUploadHint', () => {
|
|
276
|
-
it('suggests adding the slug to lightdash-app.yml when the server returned one', () => {
|
|
277
|
-
const hint = (0, appsDownload_1.preSlugUploadHint)({
|
|
278
|
-
folder: 'lightdash/apps/my-app',
|
|
279
|
-
slug: 'my-app',
|
|
280
|
-
});
|
|
281
|
-
expect(hint).toContain('lightdash/apps/my-app/lightdash-app.yml');
|
|
282
|
-
expect(hint).toContain('predates slug identity');
|
|
283
|
-
expect(hint).toContain('add `slug: my-app` to lightdash-app.yml');
|
|
284
|
-
expect(hint).toContain('Uploads keep working via uuid matching');
|
|
285
|
-
});
|
|
286
|
-
it('omits the add-slug suggestion when the server did not return a slug', () => {
|
|
287
|
-
const hint = (0, appsDownload_1.preSlugUploadHint)({
|
|
288
|
-
folder: 'lightdash/apps/my-app',
|
|
289
|
-
slug: undefined,
|
|
290
|
-
});
|
|
291
|
-
expect(hint).toContain('lightdash/apps/my-app/lightdash-app.yml');
|
|
292
|
-
expect(hint).toContain('predates slug identity');
|
|
293
|
-
expect(hint).not.toContain('add `slug:');
|
|
294
|
-
expect(hint).toContain('Uploads keep working via uuid matching');
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
describe('matchedUploadRefs', () => {
|
|
298
|
-
const manifest = {
|
|
299
|
-
codeVersion: 1,
|
|
300
|
-
appUuid: 'app-uuid-1',
|
|
301
|
-
projectUuid: 'proj-uuid-1',
|
|
302
|
-
slug: 'sales-app',
|
|
303
|
-
version: 3,
|
|
304
|
-
name: 'My App',
|
|
305
|
-
description: '',
|
|
306
|
-
template: null,
|
|
307
|
-
downloadedAt: '2026-07-29T00:00:00.000Z',
|
|
308
|
-
};
|
|
309
|
-
it('returns the filter entries the manifest satisfies', () => {
|
|
310
|
-
expect((0, appsDownload_1.matchedUploadRefs)(new Set(['app-uuid-1', 'sales-app', 'other']), manifest)).toEqual(['app-uuid-1', 'sales-app']);
|
|
311
|
-
});
|
|
312
|
-
it('returns only the slug for uuid-free manifests', () => {
|
|
313
|
-
expect((0, appsDownload_1.matchedUploadRefs)(new Set(['app-uuid-1', 'sales-app']), {
|
|
314
|
-
...manifest,
|
|
315
|
-
appUuid: undefined,
|
|
316
|
-
})).toEqual(['sales-app']);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
describe('unmatchedUploadRefsWarning', () => {
|
|
320
|
-
it('returns null when everything matched', () => {
|
|
321
|
-
expect((0, appsDownload_1.unmatchedUploadRefsWarning)([])).toBeNull();
|
|
322
|
-
});
|
|
323
|
-
it('lists unmatched refs', () => {
|
|
324
|
-
const warning = (0, appsDownload_1.unmatchedUploadRefsWarning)(['sales-ap']);
|
|
325
|
-
expect(warning).toContain('No local app folder matched: sales-ap.');
|
|
326
|
-
expect(warning).not.toContain('slug identity');
|
|
327
|
-
});
|
|
328
|
-
it('adds the slug-identity explanation for uuid-shaped refs', () => {
|
|
329
|
-
const warning = (0, appsDownload_1.unmatchedUploadRefsWarning)([
|
|
330
|
-
'd3afc44c-6f0f-4d9f-a267-fb739efa31dd',
|
|
331
|
-
]);
|
|
332
|
-
expect(warning).toContain('carry no uuid');
|
|
333
|
-
expect(warning).toContain('select them by slug');
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
describe('preSlugServerHint', () => {
|
|
337
|
-
it('warns that the server matched by uuid only and to check for duplicates', () => {
|
|
338
|
-
const hint = (0, appsDownload_1.preSlugServerHint)('my-app');
|
|
339
|
-
expect(hint).toContain('"my-app"');
|
|
340
|
-
expect(hint).toContain('predates slug-based app identity');
|
|
341
|
-
expect(hint).toContain('verify no duplicate was created');
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
describe('computeUpsertedTotal', () => {
|
|
345
|
-
it('sums only keys that are neither skipped nor failed', () => {
|
|
346
|
-
const changes = {
|
|
347
|
-
'charts created': 2,
|
|
348
|
-
'charts skipped': 3,
|
|
349
|
-
'charts failed': 1,
|
|
350
|
-
'dashboards updated': 4,
|
|
351
|
-
};
|
|
352
|
-
expect((0, appsDownload_1.computeUpsertedTotal)(changes)).toBe(6); // 2 + 4
|
|
353
|
-
});
|
|
354
|
-
it('returns 0 when all keys are skipped or failed', () => {
|
|
355
|
-
const changes = {
|
|
356
|
-
'charts skipped': 5,
|
|
357
|
-
'data apps failed': 1,
|
|
358
|
-
};
|
|
359
|
-
expect((0, appsDownload_1.computeUpsertedTotal)(changes)).toBe(0);
|
|
360
|
-
});
|
|
361
|
-
it('returns 0 for empty changes', () => {
|
|
362
|
-
expect((0, appsDownload_1.computeUpsertedTotal)({})).toBe(0);
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
describe('shouldWarnAllSkipped', () => {
|
|
366
|
-
it.each(['data apps skipped', 'chart types skipped', 'data apps failed'])('does not suggest --force for %s, even with unchanged charts', (key) => {
|
|
367
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({ [key]: 1 })).toBe(false);
|
|
368
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({ [key]: 1, 'charts skipped': 3 })).toBe(false);
|
|
369
|
-
});
|
|
370
|
-
it('returns true when everything was skipped', () => {
|
|
371
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({ 'charts skipped': 3 })).toBe(true);
|
|
372
|
-
});
|
|
373
|
-
it('returns false when some content was upserted', () => {
|
|
374
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({
|
|
375
|
-
'charts skipped': 3,
|
|
376
|
-
'data apps created': 1,
|
|
377
|
-
})).toBe(false);
|
|
378
|
-
});
|
|
379
|
-
it('returns false when only failures exist (no skipped)', () => {
|
|
380
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({ 'data apps failed': 1 })).toBe(false);
|
|
381
|
-
});
|
|
382
|
-
it('returns false when nothing was skipped', () => {
|
|
383
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({ 'charts created': 2 })).toBe(false);
|
|
384
|
-
});
|
|
385
|
-
it('returns false for empty changes', () => {
|
|
386
|
-
expect((0, appsDownload_1.shouldWarnAllSkipped)({})).toBe(false);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
describe('classifyAppDownloadError', () => {
|
|
390
|
-
const make404 = (message) => new common_1.LightdashError({
|
|
391
|
-
message,
|
|
392
|
-
name: 'NotFoundError',
|
|
393
|
-
statusCode: 404,
|
|
394
|
-
data: {},
|
|
395
|
-
});
|
|
396
|
-
it('skips apps that have no built version yet', () => {
|
|
397
|
-
expect((0, appsDownload_1.classifyAppDownloadError)(make404('Data app has no ready version yet: uuid-a'))).toEqual({ kind: 'skip-not-built' });
|
|
398
|
-
});
|
|
399
|
-
it('passes the server message through for other 404s', () => {
|
|
400
|
-
expect((0, appsDownload_1.classifyAppDownloadError)(make404('App not found'))).toEqual({
|
|
401
|
-
kind: 'fail',
|
|
402
|
-
message: 'App not found',
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
it('falls back to a canned explanation only when a 404 has no server message', () => {
|
|
406
|
-
const outcome = (0, appsDownload_1.classifyAppDownloadError)(make404(''));
|
|
407
|
-
expect(outcome.kind).toBe('fail');
|
|
408
|
-
if (outcome.kind === 'fail') {
|
|
409
|
-
expect(outcome.message).toContain('data apps');
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
it('passes the server message through for non-404 Lightdash errors', () => {
|
|
413
|
-
const err = new common_1.LightdashError({
|
|
414
|
-
message: 'Data apps are not enabled',
|
|
415
|
-
name: 'ForbiddenError',
|
|
416
|
-
statusCode: 403,
|
|
417
|
-
data: {},
|
|
418
|
-
});
|
|
419
|
-
expect((0, appsDownload_1.classifyAppDownloadError)(err)).toEqual({
|
|
420
|
-
kind: 'fail',
|
|
421
|
-
message: 'Data apps are not enabled',
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
it('fails with the error message for plain errors', () => {
|
|
425
|
-
expect((0, appsDownload_1.classifyAppDownloadError)(new Error('boom'))).toEqual({
|
|
426
|
-
kind: 'fail',
|
|
427
|
-
message: 'boom',
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
describe('appsDownloadSummary', () => {
|
|
432
|
-
it('reports success when all apps downloaded', () => {
|
|
433
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(2, 2, [], '/tmp/x/apps', 0);
|
|
434
|
-
expect(summary.ok).toBe(true);
|
|
435
|
-
expect(summary.message).toContain('Downloaded 2 of 2 data app(s)');
|
|
436
|
-
expect(summary.message).toContain('/tmp/x/apps');
|
|
437
|
-
expect(summary.message).not.toContain('skipped');
|
|
438
|
-
expect(summary.failureLines).toEqual([]);
|
|
439
|
-
});
|
|
440
|
-
it('reports a warning with per-app reasons when any app failed', () => {
|
|
441
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(0, 1, [{ appRef: 'uuid-a', message: 'server exploded' }], '/tmp/x/apps', 0);
|
|
442
|
-
expect(summary.ok).toBe(false);
|
|
443
|
-
expect(summary.message).toContain('Downloaded 0 of 1 data app(s)');
|
|
444
|
-
expect(summary.message).toContain('1 failed');
|
|
445
|
-
expect(summary.failureLines).toHaveLength(1);
|
|
446
|
-
expect(summary.failureLines[0]).toContain('uuid-a');
|
|
447
|
-
expect(summary.failureLines[0]).toContain('server exploded');
|
|
448
|
-
});
|
|
449
|
-
it('excludes skipped apps from the attempted count and stays ok', () => {
|
|
450
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(41, 50, [], '/tmp/x/apps', 9);
|
|
451
|
-
expect(summary.ok).toBe(true);
|
|
452
|
-
expect(summary.message).toContain('Downloaded 41 of 41 data app(s)');
|
|
453
|
-
expect(summary.message).toContain('9 skipped: no built version');
|
|
454
|
-
expect(summary.failureLines).toEqual([]);
|
|
455
|
-
});
|
|
456
|
-
it('reports skips and failures together', () => {
|
|
457
|
-
const summary = (0, appsDownload_1.appsDownloadSummary)(1, 3, [{ appRef: 'uuid-a', message: 'server exploded' }], '/tmp/x/apps', 1);
|
|
458
|
-
expect(summary.ok).toBe(false);
|
|
459
|
-
expect(summary.message).toContain('Downloaded 1 of 2 data app(s)');
|
|
460
|
-
expect(summary.message).toContain('1 skipped: no built version');
|
|
461
|
-
expect(summary.message).toContain('1 failed');
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
describe('downloadAppsToDir', () => {
|
|
465
|
-
const tmpDir = () => fs.mkdtempSync(path.join(os.tmpdir(), 'ld-apps-download-'));
|
|
466
|
-
const codeFor = (slug) => ({
|
|
467
|
-
manifest: {
|
|
468
|
-
codeVersion: 1,
|
|
469
|
-
projectUuid: 'project-uuid',
|
|
470
|
-
slug,
|
|
471
|
-
version: 1,
|
|
472
|
-
name: slug,
|
|
473
|
-
description: '',
|
|
474
|
-
template: null,
|
|
475
|
-
downloadedAt: '2026-07-30T00:00:00.000Z',
|
|
476
|
-
},
|
|
477
|
-
files: [
|
|
478
|
-
{
|
|
479
|
-
path: 'src/App.tsx',
|
|
480
|
-
contentBase64: Buffer.from('export default () => null;').toString('base64'),
|
|
481
|
-
},
|
|
482
|
-
],
|
|
483
|
-
context: {
|
|
484
|
-
semanticLayer: {
|
|
485
|
-
path: '.lightdash/context/semantic-layer.yml',
|
|
486
|
-
contentBase64: Buffer.from('models: []').toString('base64'),
|
|
487
|
-
},
|
|
488
|
-
parameters: null,
|
|
489
|
-
promptHistory: {
|
|
490
|
-
path: '.lightdash/context/prompt-history.md',
|
|
491
|
-
contentBase64: Buffer.from('# history').toString('base64'),
|
|
492
|
-
},
|
|
493
|
-
theme: { instructions: null, assets: [], skippedAssetCount: 0 },
|
|
494
|
-
},
|
|
495
|
-
});
|
|
496
|
-
it('writes one folder per app and counts successes', async () => {
|
|
497
|
-
const appsDir = tmpDir();
|
|
498
|
-
const outcome = await (0, appsDownload_1.downloadAppsToDir)({
|
|
499
|
-
appRefs: ['revenue-explorer'],
|
|
500
|
-
projectId: 'project-uuid',
|
|
501
|
-
appsDir,
|
|
502
|
-
takenFolders: new Set(),
|
|
503
|
-
cliVersion: '0.0.0-test',
|
|
504
|
-
fetchApp: async (_p, ref) => codeFor(ref),
|
|
505
|
-
});
|
|
506
|
-
expect(outcome).toEqual({
|
|
507
|
-
successCount: 1,
|
|
508
|
-
skippedNotBuiltCount: 0,
|
|
509
|
-
skippedWrongKindCount: 0,
|
|
510
|
-
failures: [],
|
|
511
|
-
});
|
|
512
|
-
expect(fs.existsSync(path.join(appsDir, 'revenue-explorer', 'lightdash-app.yml'))).toBe(true);
|
|
513
|
-
});
|
|
514
|
-
it('gives downloaded chart types the chart-type authoring flavor', async () => {
|
|
515
|
-
const appsDir = tmpDir();
|
|
516
|
-
const vizCode = codeFor('my-chart-type');
|
|
517
|
-
vizCode.manifest.template = 'data_app_viz';
|
|
518
|
-
const outcome = await (0, appsDownload_1.downloadAppsToDir)({
|
|
519
|
-
appRefs: ['my-chart-type'],
|
|
520
|
-
projectId: 'project-uuid',
|
|
521
|
-
appsDir,
|
|
522
|
-
takenFolders: new Set(),
|
|
523
|
-
cliVersion: '0.0.0-test',
|
|
524
|
-
fetchApp: async () => vizCode,
|
|
525
|
-
});
|
|
526
|
-
expect(outcome.successCount).toBe(1);
|
|
527
|
-
const skillsDir = path.join(appsDir, 'my-chart-type', '.claude/skills');
|
|
528
|
-
expect(fs.existsSync(path.join(skillsDir, 'developing-chart-types-locally/SKILL.md'))).toBe(true);
|
|
529
|
-
// The app SDK skills would only mislead — a viz must not query.
|
|
530
|
-
expect(fs.existsSync(path.join(skillsDir, 'lightdash-data-app/SKILL.md'))).toBe(false);
|
|
531
|
-
expect(fs.existsSync(path.join(skillsDir, 'developing-data-apps-locally/SKILL.md'))).toBe(false);
|
|
532
|
-
expect(fs
|
|
533
|
-
.readFileSync(path.join(appsDir, 'my-chart-type', 'AGENTS.md'), 'utf8')
|
|
534
|
-
.toString()).toContain('custom chart type');
|
|
535
|
-
});
|
|
536
|
-
it('skips bundles the skipBundle guard rejects without writing them', async () => {
|
|
537
|
-
const appsDir = tmpDir();
|
|
538
|
-
const vizCode = codeFor('my-chart-type');
|
|
539
|
-
vizCode.manifest.template = 'data_app_viz';
|
|
540
|
-
const outcome = await (0, appsDownload_1.downloadAppsToDir)({
|
|
541
|
-
appRefs: ['my-chart-type', 'revenue-explorer'],
|
|
542
|
-
projectId: 'project-uuid',
|
|
543
|
-
appsDir,
|
|
544
|
-
takenFolders: new Set(),
|
|
545
|
-
cliVersion: '0.0.0-test',
|
|
546
|
-
fetchApp: async (_p, ref) => ref === 'my-chart-type' ? vizCode : codeFor(ref),
|
|
547
|
-
skipBundle: (manifest) => manifest.template === 'data_app_viz'
|
|
548
|
-
? 'this is a custom chart type'
|
|
549
|
-
: null,
|
|
550
|
-
});
|
|
551
|
-
expect(outcome).toEqual({
|
|
552
|
-
successCount: 1,
|
|
553
|
-
skippedNotBuiltCount: 0,
|
|
554
|
-
skippedWrongKindCount: 1,
|
|
555
|
-
failures: [],
|
|
556
|
-
});
|
|
557
|
-
expect(fs.existsSync(path.join(appsDir, 'my-chart-type'))).toBe(false);
|
|
558
|
-
expect(fs.existsSync(path.join(appsDir, 'revenue-explorer', 'lightdash-app.yml'))).toBe(true);
|
|
559
|
-
});
|
|
560
|
-
it('writes server-provided dependencies over the scaffold template package.json', async () => {
|
|
561
|
-
const appsDir = tmpDir();
|
|
562
|
-
const packageJson = JSON.stringify({
|
|
563
|
-
name: 'server-provided',
|
|
564
|
-
dependencies: {},
|
|
565
|
-
});
|
|
566
|
-
const lockfile = 'lockfileVersion: 6.0\nserver: provided\n';
|
|
567
|
-
const outcome = await (0, appsDownload_1.downloadAppsToDir)({
|
|
568
|
-
appRefs: ['deps-app'],
|
|
569
|
-
projectId: 'project-uuid',
|
|
570
|
-
appsDir,
|
|
571
|
-
takenFolders: new Set(),
|
|
572
|
-
cliVersion: '0.0.0-test',
|
|
573
|
-
fetchApp: async (_p, ref) => ({
|
|
574
|
-
...codeFor(ref),
|
|
575
|
-
dependencies: { packageJson, lockfile },
|
|
576
|
-
}),
|
|
577
|
-
});
|
|
578
|
-
expect(outcome.successCount).toBe(1);
|
|
579
|
-
const writtenPackageJson = fs.readFileSync(path.join(appsDir, 'deps-app', 'package.json'), 'utf-8');
|
|
580
|
-
expect(writtenPackageJson).toBe(packageJson);
|
|
581
|
-
const writtenLockfile = fs.readFileSync(path.join(appsDir, 'deps-app', 'pnpm-lock.yaml'), 'utf-8');
|
|
582
|
-
expect(writtenLockfile).toBe(lockfile);
|
|
583
|
-
});
|
|
584
|
-
it('counts an app with no ready version as skipped, not failed', async () => {
|
|
585
|
-
const outcome = await (0, appsDownload_1.downloadAppsToDir)({
|
|
586
|
-
appRefs: ['half-built'],
|
|
587
|
-
projectId: 'project-uuid',
|
|
588
|
-
appsDir: tmpDir(),
|
|
589
|
-
takenFolders: new Set(),
|
|
590
|
-
cliVersion: '0.0.0-test',
|
|
591
|
-
fetchApp: async () => {
|
|
592
|
-
throw new common_1.LightdashError({
|
|
593
|
-
message: 'App has no ready version to download',
|
|
594
|
-
name: 'NotFoundError',
|
|
595
|
-
statusCode: 404,
|
|
596
|
-
data: {},
|
|
597
|
-
});
|
|
598
|
-
},
|
|
599
|
-
});
|
|
600
|
-
expect(outcome.skippedNotBuiltCount).toBe(1);
|
|
601
|
-
expect(outcome.failures).toEqual([]);
|
|
602
|
-
});
|
|
603
|
-
});
|
|
604
|
-
describe('resolveUploadFilterUuids', () => {
|
|
605
|
-
const appUuid = 'd3afc44c-6f0f-4d9f-a267-fb739efa31dd';
|
|
606
|
-
const otherUuid = 'a1b2c3d4-0000-4000-8000-000000000000';
|
|
607
|
-
it('translates uuid refs the listing knows into slugs', () => {
|
|
608
|
-
const resolved = (0, appsDownload_1.resolveUploadFilterUuids)(new Set([appUuid]), [
|
|
609
|
-
{ appUuid, slug: 'my-app' },
|
|
610
|
-
]);
|
|
611
|
-
expect(resolved).toEqual(new Set(['my-app']));
|
|
612
|
-
});
|
|
613
|
-
it('keeps slug refs and unknown uuids untouched', () => {
|
|
614
|
-
const resolved = (0, appsDownload_1.resolveUploadFilterUuids)(new Set(['my-app', otherUuid]), [{ appUuid, slug: 'my-app' }]);
|
|
615
|
-
expect(resolved).toEqual(new Set(['my-app', otherUuid]));
|
|
616
|
-
});
|
|
617
|
-
it('leaves uuid refs alone when the listing has no slugs (pre-slug server)', () => {
|
|
618
|
-
const resolved = (0, appsDownload_1.resolveUploadFilterUuids)(new Set([appUuid]), [
|
|
619
|
-
{ appUuid },
|
|
620
|
-
]);
|
|
621
|
-
expect(resolved).toEqual(new Set([appUuid]));
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
describe('resolveAppSpaceUuid', () => {
|
|
625
|
-
const spaces = [
|
|
626
|
-
{ uuid: 'space-1', slug: 'marketing' },
|
|
627
|
-
{ uuid: 'space-2', slug: 'finance' },
|
|
628
|
-
{ uuid: 'space-3', slug: 'finance' },
|
|
629
|
-
];
|
|
630
|
-
it('resolves a unique slug to its uuid', () => {
|
|
631
|
-
expect((0, appsDownload_1.resolveAppSpaceUuid)('marketing', spaces)).toBe('space-1');
|
|
632
|
-
});
|
|
633
|
-
it('fails loudly when no space matches the slug', () => {
|
|
634
|
-
expect(() => (0, appsDownload_1.resolveAppSpaceUuid)('missing', spaces)).toThrow(/no space with slug "missing"/);
|
|
635
|
-
});
|
|
636
|
-
it('fails loudly when the slug is ambiguous', () => {
|
|
637
|
-
expect(() => (0, appsDownload_1.resolveAppSpaceUuid)('finance', spaces)).toThrow(/multiple spaces match/);
|
|
638
|
-
});
|
|
639
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"build.test.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/build.test.ts"],"names":[],"mappings":""}
|