@malloy-publisher/server 0.0.382-dev → 0.2.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/README.docker.md +145 -20
- package/README.md +65 -12
- package/dist/app/api-doc.html +5 -0
- package/dist/app/api-doc.yaml +3903 -583
- package/dist/app/assets/EnvironmentPage-DiUgLuCH.js +1 -0
- package/dist/app/assets/HomePage-MhhjhnFj.js +1 -0
- package/dist/app/assets/LightMode-BEyvNUzT.js +1 -0
- package/dist/app/assets/MainPage-D4Cfee9o.js +2 -0
- package/dist/app/assets/MaterializationsPage-nGgGqwSN.js +1 -0
- package/dist/app/assets/ModelPage-DLAcEw-F.js +1 -0
- package/dist/app/assets/PackagePage-Df0ikH7w.js +1 -0
- package/dist/app/assets/RouteError-BFQwtroH.js +1 -0
- package/dist/app/assets/ThemeEditorPage-OoskoQYN.js +1 -0
- package/dist/app/assets/WorkbookPage-CpFgvUDC.js +1 -0
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-lb28vQkR.es-BoVSOc75.js} +1 -1
- package/dist/app/assets/github-dark-DenFmJkN.es-DHJKELXO.js +1 -0
- package/dist/app/assets/index-5eLCcNmP.css +1 -0
- package/dist/app/assets/index-6ypuFH6g.js +1774 -0
- package/dist/app/assets/index-B8pnU_V_.js +615 -0
- package/dist/app/assets/index-BImcHmVP.js +1 -0
- package/dist/app/assets/index-DSoCUF-f.js +2725 -0
- package/dist/app/assets/index-DfqzSItA.js +2 -0
- package/dist/app/assets/index-w_0OQJgZ.js +23 -0
- package/dist/app/index.html +8 -4
- package/dist/cpufeatures-1yrn0vtw.node +0 -0
- package/dist/default-publisher.config.json +23 -0
- package/dist/{instrumentation.js → instrumentation.mjs} +10631 -10573
- package/dist/package_load_worker.mjs +15219 -0
- package/dist/runtime/publisher.js +331 -0
- package/dist/{server.js → server.mjs} +115548 -49590
- package/dist/sshcrypto-vd2k5hq9.node +0 -0
- package/package.json +41 -28
- package/publisher.config.example.bigquery.json +33 -0
- package/publisher.config.example.duckdb.json +23 -0
- package/.eslintrc.json +0 -44
- package/.prettierignore +0 -1
- package/.prettierrc +0 -4
- package/build.ts +0 -38
- package/dist/app/assets/HomePage-Dn3E4CuB.js +0 -1
- package/dist/app/assets/MainPage-BzB3yoqi.js +0 -2
- package/dist/app/assets/ModelPage-C9O_sAXT.js +0 -1
- package/dist/app/assets/PackagePage-DcxKEjBX.js +0 -1
- package/dist/app/assets/ProjectPage-BDj307rF.js +0 -1
- package/dist/app/assets/RouteError-DAShbVCG.js +0 -1
- package/dist/app/assets/WorkbookPage-Cs_XYEaB.js +0 -1
- package/dist/app/assets/index-15BOvhp0.js +0 -456
- package/dist/app/assets/index-Bb2jqquW.js +0 -676
- package/dist/app/assets/index-CMlGQMcl.css +0 -1
- package/dist/app/assets/index-D68X76-7.js +0 -1276
- package/dist/app/assets/index.umd-DGBekgSu.js +0 -1145
- package/dxt/malloy_bridge.py +0 -354
- package/dxt/manifest.json +0 -22
- package/eslint.config.mjs +0 -76
- package/k6-tests/README.md +0 -144
- package/k6-tests/bun.lock +0 -797
- package/k6-tests/load-test/load-test-crud-connections.ts +0 -263
- package/k6-tests/load-test/load-test-crud-packages.ts +0 -461
- package/k6-tests/load-test/load-test-crud-projects.ts +0 -160
- package/k6-tests/load-test/load-test-crud.ts +0 -142
- package/k6-tests/load-test/load-test.ts +0 -784
- package/k6-tests/package.json +0 -27
- package/k6-tests/scripts/run-with-otel.sh +0 -50
- package/k6-tests/smoke-test/smoke-test.ts +0 -158
- package/k6-tests/tsconfig.json +0 -29
- package/k6-tests/utils/client_factory.ts +0 -128
- package/k6-tests/utils/common.ts +0 -489
- package/k6-tests/utils/logger.ts +0 -62
- package/k6-tests/utils/types.d.ts +0 -35
- package/malloy_mcp.dxt +0 -0
- package/publisher.config.json +0 -33
- package/src/config.spec.ts +0 -850
- package/src/config.ts +0 -305
- package/src/constants.ts +0 -10
- package/src/controller/compile.controller.ts +0 -35
- package/src/controller/connection.controller.ts +0 -515
- package/src/controller/database.controller.ts +0 -21
- package/src/controller/manifest.controller.ts +0 -29
- package/src/controller/materialization.controller.ts +0 -125
- package/src/controller/model.controller.ts +0 -107
- package/src/controller/package.controller.ts +0 -182
- package/src/controller/query.controller.ts +0 -60
- package/src/controller/watch-mode.controller.ts +0 -98
- package/src/data_styles.ts +0 -71
- package/src/dto/connection.dto.spec.ts +0 -131
- package/src/dto/connection.dto.ts +0 -174
- package/src/dto/index.ts +0 -2
- package/src/dto/package.dto.spec.ts +0 -42
- package/src/dto/package.dto.ts +0 -12
- package/src/dto/validate.spec.ts +0 -76
- package/src/dto/validate.ts +0 -31
- package/src/errors.ts +0 -116
- package/src/health.ts +0 -189
- package/src/instrumentation.ts +0 -148
- package/src/logger.ts +0 -154
- package/src/mcp/error_messages.ts +0 -199
- package/src/mcp/handler_utils.ts +0 -239
- package/src/mcp/mcp_constants.ts +0 -22
- package/src/mcp/prompts/handlers.ts +0 -84
- package/src/mcp/prompts/index.ts +0 -11
- package/src/mcp/prompts/prompt_definitions.ts +0 -160
- package/src/mcp/prompts/prompt_service.ts +0 -67
- package/src/mcp/prompts/utils.ts +0 -62
- package/src/mcp/resource_metadata.ts +0 -47
- package/src/mcp/resources/model_resource.ts +0 -153
- package/src/mcp/resources/notebook_resource.ts +0 -137
- package/src/mcp/resources/package_resource.ts +0 -370
- package/src/mcp/resources/project_resource.ts +0 -184
- package/src/mcp/resources/query_resource.ts +0 -117
- package/src/mcp/resources/source_resource.ts +0 -141
- package/src/mcp/resources/view_resource.ts +0 -136
- package/src/mcp/server.ts +0 -62
- package/src/mcp/tools/discovery_tools.ts +0 -258
- package/src/mcp/tools/execute_query_tool.ts +0 -265
- package/src/server.ts +0 -1195
- package/src/service/connection.spec.ts +0 -1331
- package/src/service/connection.ts +0 -1511
- package/src/service/connection_service.spec.ts +0 -641
- package/src/service/connection_service.ts +0 -181
- package/src/service/db_utils.spec.ts +0 -714
- package/src/service/db_utils.ts +0 -1227
- package/src/service/filter.spec.ts +0 -447
- package/src/service/filter.ts +0 -337
- package/src/service/filter_integration.spec.ts +0 -825
- package/src/service/gcs_s3_utils.ts +0 -427
- package/src/service/manifest_service.spec.ts +0 -201
- package/src/service/manifest_service.ts +0 -106
- package/src/service/materialization_service.spec.ts +0 -648
- package/src/service/materialization_service.ts +0 -929
- package/src/service/materialized_table_gc.spec.ts +0 -383
- package/src/service/materialized_table_gc.ts +0 -279
- package/src/service/model.spec.ts +0 -257
- package/src/service/model.ts +0 -1069
- package/src/service/package.spec.ts +0 -236
- package/src/service/package.ts +0 -463
- package/src/service/project.ts +0 -681
- package/src/service/project_compile.spec.ts +0 -197
- package/src/service/project_store.spec.ts +0 -859
- package/src/service/project_store.ts +0 -1531
- package/src/service/quoting.ts +0 -41
- package/src/service/resolve_project.ts +0 -13
- package/src/storage/BaseRepository.ts +0 -31
- package/src/storage/DatabaseInterface.ts +0 -180
- package/src/storage/StorageManager.mock.ts +0 -59
- package/src/storage/StorageManager.ts +0 -210
- package/src/storage/duckdb/ConnectionRepository.ts +0 -141
- package/src/storage/duckdb/DuckDBConnection.ts +0 -177
- package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
- package/src/storage/duckdb/DuckDBRepository.ts +0 -224
- package/src/storage/duckdb/ManifestRepository.ts +0 -119
- package/src/storage/duckdb/MaterializationRepository.ts +0 -249
- package/src/storage/duckdb/PackageRepository.ts +0 -135
- package/src/storage/duckdb/ProjectRepository.ts +0 -148
- package/src/storage/duckdb/manifest_store.spec.ts +0 -133
- package/src/storage/duckdb/schema.ts +0 -147
- package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -146
- package/src/utils.ts +0 -13
- package/tests/fixtures/persist-test/data/orders.csv +0 -5
- package/tests/fixtures/persist-test/persist_test.malloy +0 -11
- package/tests/fixtures/persist-test/publisher.json +0 -5
- package/tests/fixtures/publisher.config.json +0 -15
- package/tests/harness/e2e.ts +0 -96
- package/tests/harness/mcp_test_setup.ts +0 -344
- package/tests/harness/mocks.ts +0 -37
- package/tests/harness/rest_e2e.ts +0 -68
- package/tests/harness/uris.ts +0 -31
- package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +0 -470
- package/tests/integration/mcp/mcp_execute_query_tool.integration.spec.ts +0 -434
- package/tests/integration/mcp/mcp_resource.integration.spec.ts +0 -663
- package/tests/integration/mcp/mcp_transport.integration.spec.ts +0 -122
- package/tests/integration/mcp/setup.spec.ts +0 -5
- package/tests/unit/duckdb/attached_databases.test.ts +0 -1132
- package/tests/unit/ducklake/ducklake.test.ts +0 -950
- package/tests/unit/mcp/prompt_definitions.test.ts +0 -102
- package/tests/unit/mcp/prompt_happy.test.ts +0 -51
- package/tsconfig.json +0 -32
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
import { check, group, sleep } from "k6";
|
|
2
|
-
import {
|
|
3
|
-
getConnectionsClient,
|
|
4
|
-
getPackagesClient,
|
|
5
|
-
getProjectsClient,
|
|
6
|
-
} from "../utils/client_factory.ts";
|
|
7
|
-
import type { Connection } from "../clients/malloyPublisherSemanticModelServingAPI.schemas.ts";
|
|
8
|
-
import { ConnectionType as ConnectionTypeEnum } from "../clients/malloyPublisherSemanticModelServingAPI.schemas.ts";
|
|
9
|
-
import {
|
|
10
|
-
AUTH_TOKEN,
|
|
11
|
-
BASE_URL,
|
|
12
|
-
generateTestName,
|
|
13
|
-
getAvailablePackages,
|
|
14
|
-
HAS_BIGQUERY_CREDENTIALS,
|
|
15
|
-
validateServerIsUpAndInitialized,
|
|
16
|
-
} from "../utils/common.ts";
|
|
17
|
-
|
|
18
|
-
const projectsClient = getProjectsClient(BASE_URL, AUTH_TOKEN);
|
|
19
|
-
const connectionsClient = getConnectionsClient(BASE_URL, AUTH_TOKEN);
|
|
20
|
-
const packagesClient = getPackagesClient(BASE_URL, AUTH_TOKEN);
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Setup data structure for packages test
|
|
24
|
-
*/
|
|
25
|
-
type SetupData = {
|
|
26
|
-
projectName: string;
|
|
27
|
-
bigqueryConnectionName: string | null;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Setup function - runs once before all VUs
|
|
32
|
-
* Creates a project and a BigQuery connection (if credentials available)
|
|
33
|
-
*/
|
|
34
|
-
export function setup(): SetupData {
|
|
35
|
-
// Validate server is up and initialized before proceeding
|
|
36
|
-
validateServerIsUpAndInitialized();
|
|
37
|
-
|
|
38
|
-
console.log("Setup: Starting package load test setup...");
|
|
39
|
-
const projectName = generateTestName("load-test-pkg-project");
|
|
40
|
-
console.log(`Setup: Creating project ${projectName}...`);
|
|
41
|
-
|
|
42
|
-
const setupProjectResponse = projectsClient.createProject(
|
|
43
|
-
{
|
|
44
|
-
name: projectName,
|
|
45
|
-
},
|
|
46
|
-
{ tags: { name: "setup_create_project" } },
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const status = setupProjectResponse.response.status;
|
|
50
|
-
console.log(`Setup: Project creation response status: ${status}`);
|
|
51
|
-
|
|
52
|
-
if (status !== 200 && status !== 201) {
|
|
53
|
-
const errorBody =
|
|
54
|
-
setupProjectResponse.response.body || "no error details";
|
|
55
|
-
const errorMessage =
|
|
56
|
-
status === 501
|
|
57
|
-
? `Project creation not available (config may be frozen or endpoint not implemented). Status: ${status}`
|
|
58
|
-
: `Failed to setup project for packages. Status: ${status}, Body: ${errorBody}`;
|
|
59
|
-
throw new Error(errorMessage);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
console.log(
|
|
63
|
-
`Setup: Project ${projectName} created successfully, verifying...`,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Verify project was created and is accessible
|
|
67
|
-
const verifyProjectResponse = projectsClient.getProject(
|
|
68
|
-
projectName,
|
|
69
|
-
undefined,
|
|
70
|
-
{ tags: { name: "setup_verify_project" } },
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (verifyProjectResponse.response.status !== 200) {
|
|
74
|
-
const errorBody =
|
|
75
|
-
verifyProjectResponse.response.body || "no error details";
|
|
76
|
-
throw new Error(
|
|
77
|
-
`Project ${projectName} was created but cannot be accessed. Status: ${verifyProjectResponse.response.status}, Body: ${errorBody}. This may indicate the database was reset during setup.`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const createdProject = verifyProjectResponse.data;
|
|
82
|
-
console.log(
|
|
83
|
-
`Setup: Created and verified project ${projectName} for packages testing. Location: ${createdProject.location || "default"}. All VUs will use this same project.`,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
let bigqueryConnectionName: string | null = null;
|
|
87
|
-
|
|
88
|
-
// Setup BigQuery connection if credentials are available
|
|
89
|
-
if (HAS_BIGQUERY_CREDENTIALS) {
|
|
90
|
-
console.log(
|
|
91
|
-
"Setup: BigQuery credentials available, creating connection...",
|
|
92
|
-
);
|
|
93
|
-
bigqueryConnectionName = "bigquery";
|
|
94
|
-
const bigqueryConnection: Connection = {
|
|
95
|
-
name: bigqueryConnectionName,
|
|
96
|
-
type: ConnectionTypeEnum.bigquery,
|
|
97
|
-
attributes: {
|
|
98
|
-
dialectName: "bigquery",
|
|
99
|
-
isPool: false,
|
|
100
|
-
canPersist: true,
|
|
101
|
-
canStream: true,
|
|
102
|
-
},
|
|
103
|
-
bigqueryConnection: {
|
|
104
|
-
location: "US",
|
|
105
|
-
serviceAccountKeyJson: __ENV.GOOGLE_APPLICATION_CREDENTIALS,
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
console.log(
|
|
110
|
-
`Setup: Calling createConnection for ${bigqueryConnectionName}...`,
|
|
111
|
-
);
|
|
112
|
-
const createConnectionResponse = connectionsClient.createConnection(
|
|
113
|
-
projectName,
|
|
114
|
-
bigqueryConnectionName,
|
|
115
|
-
bigqueryConnection,
|
|
116
|
-
{ tags: { name: "setup_create_connection" } },
|
|
117
|
-
);
|
|
118
|
-
console.log(
|
|
119
|
-
`Setup: createConnection response status: ${createConnectionResponse.response.status}`,
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
createConnectionResponse.response.status !== 200 &&
|
|
124
|
-
createConnectionResponse.response.status !== 201
|
|
125
|
-
) {
|
|
126
|
-
console.warn(
|
|
127
|
-
`Failed to create BigQuery connection: ${createConnectionResponse.response.status} - ${createConnectionResponse.response.body}. Continuing without BigQuery connection.`,
|
|
128
|
-
);
|
|
129
|
-
bigqueryConnectionName = null;
|
|
130
|
-
} else {
|
|
131
|
-
console.log(`Setup: BigQuery connection created, verifying...`);
|
|
132
|
-
// Verify connection was created successfully
|
|
133
|
-
const getConnectionResponse = connectionsClient.getConnection(
|
|
134
|
-
projectName,
|
|
135
|
-
bigqueryConnectionName,
|
|
136
|
-
{ tags: { name: "setup_verify_connection" } },
|
|
137
|
-
);
|
|
138
|
-
console.log(
|
|
139
|
-
`Setup: getConnection response status: ${getConnectionResponse.response.status}`,
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
if (getConnectionResponse.response.status !== 200) {
|
|
143
|
-
console.warn(
|
|
144
|
-
`BigQuery connection creation succeeded but verification failed (status ${getConnectionResponse.response.status}). Setting bigqueryConnectionName to null to skip BigQuery packages.`,
|
|
145
|
-
);
|
|
146
|
-
bigqueryConnectionName = null;
|
|
147
|
-
} else {
|
|
148
|
-
console.log(
|
|
149
|
-
`Setup: Created and verified BigQuery connection ${bigqueryConnectionName} for project ${projectName}`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
console.log(
|
|
155
|
-
"Setup: BigQuery credentials not available, skipping BigQuery connection",
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.log(
|
|
160
|
-
`Setup: Completed. Returning projectName: ${projectName}, bigqueryConnectionName: ${bigqueryConnectionName}`,
|
|
161
|
-
);
|
|
162
|
-
return { projectName, bigqueryConnectionName };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Teardown function - runs once after all VUs
|
|
167
|
-
* Cleans up the connection and project created in setup
|
|
168
|
-
* Gracefully handles cases where server is down
|
|
169
|
-
*/
|
|
170
|
-
export function teardown(data: SetupData) {
|
|
171
|
-
// Teardown runs once at the end of the entire test execution (not per iteration)
|
|
172
|
-
// All packages created during iterations are already deleted in each iteration
|
|
173
|
-
// Deleting the project will cascade delete all connections (including BigQuery)
|
|
174
|
-
// via deleteConnectionsByProjectId in the database
|
|
175
|
-
if (data && data.projectName) {
|
|
176
|
-
console.log(
|
|
177
|
-
`Teardown: Deleting project ${data.projectName} (this will cascade delete all connections including ${data.bigqueryConnectionName || "none"} and any remaining packages via database cascade delete)`,
|
|
178
|
-
);
|
|
179
|
-
try {
|
|
180
|
-
const deleteResponse = projectsClient.deleteProject(data.projectName, {
|
|
181
|
-
tags: { name: "teardown_delete_project" },
|
|
182
|
-
});
|
|
183
|
-
const deleteStatus = deleteResponse.response.status;
|
|
184
|
-
if (deleteStatus === 0) {
|
|
185
|
-
console.warn(
|
|
186
|
-
`Teardown: Could not connect to server to delete project ${data.projectName} (server may be down)`,
|
|
187
|
-
);
|
|
188
|
-
} else if (deleteStatus !== 200 && deleteStatus !== 204) {
|
|
189
|
-
const errorBody =
|
|
190
|
-
typeof deleteResponse.response.body === "string"
|
|
191
|
-
? deleteResponse.response.body
|
|
192
|
-
: String(deleteResponse.response.body || "");
|
|
193
|
-
console.warn(
|
|
194
|
-
`Teardown: Failed to delete project ${data.projectName}: HTTP ${deleteStatus}. Response: ${errorBody}`,
|
|
195
|
-
);
|
|
196
|
-
} else {
|
|
197
|
-
console.log(
|
|
198
|
-
`Teardown: Successfully deleted project ${data.projectName} (status ${deleteStatus}). All connections and packages were cascade deleted.`,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
} catch (error) {
|
|
202
|
-
console.warn(
|
|
203
|
-
`Teardown: Error deleting project ${data.projectName}: ${error}`,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Load Test - CRUD Packages
|
|
211
|
-
*
|
|
212
|
-
* This test verifies system performance for package CRUD operations.
|
|
213
|
-
* A single project and BigQuery connection (if available) are created at the beginning
|
|
214
|
-
* and reused across all iterations. Cleanup happens at the end of the test run.
|
|
215
|
-
*
|
|
216
|
-
* Default configuration:
|
|
217
|
-
* - Stages with ramp-up to 100 VUs
|
|
218
|
-
* - 95th percentile response time < 1.5s
|
|
219
|
-
* - Error rate < 2%
|
|
220
|
-
*/
|
|
221
|
-
export const loadTestPackages: TestPreset = {
|
|
222
|
-
defaultOptions: {
|
|
223
|
-
stages: [
|
|
224
|
-
{ duration: "1m", target: 10 }, // warm-up
|
|
225
|
-
{ duration: "5m", target: 25 }, // sustained load
|
|
226
|
-
{ duration: "1m", target: 0 }, // ramp down
|
|
227
|
-
],
|
|
228
|
-
thresholds: {
|
|
229
|
-
// Global thresholds
|
|
230
|
-
http_req_duration: ["p(90)<1000", "p(95)<1500", "p(99)<2500"],
|
|
231
|
-
http_req_waiting: ["p(95)<1200"],
|
|
232
|
-
http_req_failed: ["rate<0.02"],
|
|
233
|
-
checks: ["rate>0.98"],
|
|
234
|
-
dropped_iterations: ["count==0"],
|
|
235
|
-
// Per-operation thresholds (C, R, U, D)
|
|
236
|
-
"http_req_duration{name:create_package}": [
|
|
237
|
-
"p(90)<1000",
|
|
238
|
-
"p(95)<1500",
|
|
239
|
-
"p(99)<2500",
|
|
240
|
-
],
|
|
241
|
-
"http_req_duration{name:get_package}": [
|
|
242
|
-
"p(90)<1000",
|
|
243
|
-
"p(95)<1500",
|
|
244
|
-
"p(99)<2500",
|
|
245
|
-
],
|
|
246
|
-
"http_req_duration{name:list_packages}": [
|
|
247
|
-
"p(90)<1000",
|
|
248
|
-
"p(95)<1500",
|
|
249
|
-
"p(99)<2500",
|
|
250
|
-
],
|
|
251
|
-
"http_req_duration{name:update_package}": [
|
|
252
|
-
"p(90)<1000",
|
|
253
|
-
"p(95)<1500",
|
|
254
|
-
"p(99)<2500",
|
|
255
|
-
],
|
|
256
|
-
"http_req_duration{name:delete_package}": [
|
|
257
|
-
"p(90)<1000",
|
|
258
|
-
"p(95)<1500",
|
|
259
|
-
"p(99)<2500",
|
|
260
|
-
],
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
run: (data: unknown | undefined) => {
|
|
264
|
-
const setupData = data as SetupData;
|
|
265
|
-
if (!setupData || !setupData.projectName) {
|
|
266
|
-
console.error("Setup data not available, skipping package CRUD test");
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const { projectName, bigqueryConnectionName } = setupData;
|
|
271
|
-
|
|
272
|
-
// Ensure we're using the same project and connection throughout the test
|
|
273
|
-
// All VUs will use the projectName and bigqueryConnectionName from setup
|
|
274
|
-
if (!projectName) {
|
|
275
|
-
console.error("Project name not available from setup");
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Verify project still exists before proceeding (defensive check)
|
|
280
|
-
const verifyProjectResponse = projectsClient.getProject(
|
|
281
|
-
projectName,
|
|
282
|
-
undefined,
|
|
283
|
-
{ tags: { name: "verify_project_before_use" } },
|
|
284
|
-
);
|
|
285
|
-
if (verifyProjectResponse.response.status !== 200) {
|
|
286
|
-
console.error(
|
|
287
|
-
`Project ${projectName} is not accessible (status ${verifyProjectResponse.response.status}). Skipping this iteration. This may indicate the project was deleted or the server was reset.`,
|
|
288
|
-
);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
// Small delay after verification to reduce rapid requests
|
|
292
|
-
sleep(0.2);
|
|
293
|
-
|
|
294
|
-
// Get available packages that are both in the directory and whitelisted
|
|
295
|
-
// Filter out BigQuery packages if BigQuery connection is not available
|
|
296
|
-
let availablePackages = getAvailablePackages();
|
|
297
|
-
if (!bigqueryConnectionName) {
|
|
298
|
-
availablePackages = availablePackages.filter(
|
|
299
|
-
(pkg) => !pkg.startsWith("bigquery-"),
|
|
300
|
-
);
|
|
301
|
-
console.log(
|
|
302
|
-
`No BigQuery connection available. Filtered out BigQuery packages. Using project: ${projectName}`,
|
|
303
|
-
);
|
|
304
|
-
} else {
|
|
305
|
-
console.log(
|
|
306
|
-
`Using project: ${projectName} with BigQuery connection: ${bigqueryConnectionName}`,
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (availablePackages.length === 0) {
|
|
311
|
-
console.warn(
|
|
312
|
-
"No available whitelisted packages found, skipping package CRUD tests",
|
|
313
|
-
);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Pick a package from available whitelisted packages (cycle through them)
|
|
318
|
-
// Use VU iteration number to cycle through packages
|
|
319
|
-
const packageIndex = __VU % availablePackages.length;
|
|
320
|
-
const selectedPackage = availablePackages[packageIndex];
|
|
321
|
-
if (!selectedPackage) {
|
|
322
|
-
console.error("No package selected for load testing");
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Construct absolute path to the selected package
|
|
327
|
-
const basePath = __ENV.PWD || "";
|
|
328
|
-
const packageLocation = basePath
|
|
329
|
-
? `${basePath}/packages/${selectedPackage}`
|
|
330
|
-
: `./packages/${selectedPackage}`;
|
|
331
|
-
|
|
332
|
-
// Delay before package creation to avoid rapid-fire requests
|
|
333
|
-
sleep(0.2);
|
|
334
|
-
|
|
335
|
-
group("Packages CRUD", () => {
|
|
336
|
-
// Create package with real location
|
|
337
|
-
group("Create Package", () => {
|
|
338
|
-
const createPackageResponse = packagesClient.createPackage(
|
|
339
|
-
projectName,
|
|
340
|
-
{
|
|
341
|
-
name: selectedPackage,
|
|
342
|
-
description: `Test package for load testing`,
|
|
343
|
-
location: packageLocation,
|
|
344
|
-
},
|
|
345
|
-
{ tags: { name: "create_package" } },
|
|
346
|
-
);
|
|
347
|
-
const createStatus = createPackageResponse.response.status;
|
|
348
|
-
check(createPackageResponse.response, {
|
|
349
|
-
"create package request successful": (r) =>
|
|
350
|
-
r.status === 200 || r.status === 201,
|
|
351
|
-
});
|
|
352
|
-
// Skip remaining operations if server is down
|
|
353
|
-
if (createStatus === 0) {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (createStatus !== 200 && createStatus !== 201) {
|
|
357
|
-
// Log error details for debugging
|
|
358
|
-
const responseBody = createPackageResponse.response.body;
|
|
359
|
-
const errorBody =
|
|
360
|
-
typeof responseBody === "string"
|
|
361
|
-
? responseBody
|
|
362
|
-
: responseBody instanceof ArrayBuffer
|
|
363
|
-
? new TextDecoder().decode(responseBody)
|
|
364
|
-
: String(responseBody || "");
|
|
365
|
-
const isConnectionError =
|
|
366
|
-
errorBody.includes("connection") ||
|
|
367
|
-
errorBody.includes("Connection") ||
|
|
368
|
-
errorBody.includes("compiling model") ||
|
|
369
|
-
errorBody.includes("Error(s) compiling");
|
|
370
|
-
if (
|
|
371
|
-
isConnectionError &&
|
|
372
|
-
selectedPackage.startsWith("bigquery-")
|
|
373
|
-
) {
|
|
374
|
-
console.warn(
|
|
375
|
-
`Package ${selectedPackage} failed due to connection/compilation error (status ${createStatus}) in project ${projectName}. This may indicate the BigQuery connection ${bigqueryConnectionName || "N/A"} is not properly configured.`,
|
|
376
|
-
);
|
|
377
|
-
} else {
|
|
378
|
-
console.warn(
|
|
379
|
-
`Package ${selectedPackage} creation failed with status ${createStatus} in project ${projectName}. Server should have cleaned up the directory.`,
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
return; // Skip remaining tests if create failed, but continue with same project/connection
|
|
383
|
-
}
|
|
384
|
-
// Wait after package creation to allow compilation/loading to complete
|
|
385
|
-
sleep(0.2);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// Read package
|
|
389
|
-
group("Get Package", () => {
|
|
390
|
-
const getPackageResponse = packagesClient.getPackage(
|
|
391
|
-
projectName,
|
|
392
|
-
selectedPackage,
|
|
393
|
-
undefined,
|
|
394
|
-
{ tags: { name: "get_package" } },
|
|
395
|
-
);
|
|
396
|
-
const getStatus = getPackageResponse.response.status;
|
|
397
|
-
check(getPackageResponse.response, {
|
|
398
|
-
"get package request successful": (r) => r.status === 200,
|
|
399
|
-
});
|
|
400
|
-
if (getStatus === 0) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
sleep(1); // Delay between operations
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// List packages
|
|
407
|
-
group("List Packages", () => {
|
|
408
|
-
const listPackagesResponse = packagesClient.listPackages(
|
|
409
|
-
projectName,
|
|
410
|
-
{ tags: { name: "list_packages" } },
|
|
411
|
-
);
|
|
412
|
-
const listStatus = listPackagesResponse.response.status;
|
|
413
|
-
check(listPackagesResponse.response, {
|
|
414
|
-
"list packages request successful": (r) => r.status === 200,
|
|
415
|
-
});
|
|
416
|
-
if (listStatus === 0) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
sleep(0.2); // Delay between operations
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// Update package
|
|
423
|
-
group("Update Package", () => {
|
|
424
|
-
const updatePackageResponse = packagesClient.updatePackage(
|
|
425
|
-
projectName,
|
|
426
|
-
selectedPackage,
|
|
427
|
-
{
|
|
428
|
-
name: selectedPackage,
|
|
429
|
-
description: "Updated description for load testing",
|
|
430
|
-
},
|
|
431
|
-
{ tags: { name: "update_package" } },
|
|
432
|
-
);
|
|
433
|
-
const updateStatus = updatePackageResponse.response.status;
|
|
434
|
-
check(updatePackageResponse.response, {
|
|
435
|
-
"update package request successful": (r) => r.status === 200,
|
|
436
|
-
});
|
|
437
|
-
if (updateStatus === 0) {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
sleep(0.2); // Delay before delete to allow update to complete
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Delete package
|
|
444
|
-
group("Delete Package", () => {
|
|
445
|
-
const deletePackageResponse = packagesClient.deletePackage(
|
|
446
|
-
projectName,
|
|
447
|
-
selectedPackage,
|
|
448
|
-
{ tags: { name: "delete_package" } },
|
|
449
|
-
);
|
|
450
|
-
check(deletePackageResponse.response, {
|
|
451
|
-
"delete package request successful": (r) =>
|
|
452
|
-
r.status === 200 || r.status === 204,
|
|
453
|
-
});
|
|
454
|
-
sleep(0.2); // Wait after delete to allow cleanup
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
},
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
export const options = loadTestPackages.defaultOptions;
|
|
461
|
-
export default loadTestPackages.run;
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { check, group, sleep } from "k6";
|
|
2
|
-
import { getProjectsClient } from "../utils/client_factory.ts";
|
|
3
|
-
import { AUTH_TOKEN, BASE_URL, generateTestName } from "../utils/common.ts";
|
|
4
|
-
|
|
5
|
-
const projectsClient = getProjectsClient(BASE_URL, AUTH_TOKEN);
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Load Test - CRUD Projects
|
|
9
|
-
*
|
|
10
|
-
* This test verifies system performance for project CRUD operations.
|
|
11
|
-
* Each iteration performs a full CRUD cycle on a project.
|
|
12
|
-
*
|
|
13
|
-
* Default configuration:
|
|
14
|
-
* - Stages with ramp-up to 100 VUs
|
|
15
|
-
* - 95th percentile response time < 1.5s
|
|
16
|
-
* - Error rate < 2%
|
|
17
|
-
*/
|
|
18
|
-
export const loadTestProjects: TestPreset = {
|
|
19
|
-
defaultOptions: {
|
|
20
|
-
stages: [
|
|
21
|
-
{ duration: "1m", target: 10 }, // warm-up
|
|
22
|
-
{ duration: "5m", target: 25 }, // sustained load
|
|
23
|
-
{ duration: "1m", target: 0 }, // ramp down
|
|
24
|
-
],
|
|
25
|
-
thresholds: {
|
|
26
|
-
// Global thresholds
|
|
27
|
-
http_req_duration: ["p(90)<1000", "p(95)<1500", "p(99)<2500"],
|
|
28
|
-
http_req_waiting: ["p(95)<1200"],
|
|
29
|
-
http_req_failed: ["rate<0.02"],
|
|
30
|
-
checks: ["rate>0.98"],
|
|
31
|
-
dropped_iterations: ["count==0"],
|
|
32
|
-
// Per-operation thresholds (C, R, U, D)
|
|
33
|
-
"http_req_duration{name:create_project}": [
|
|
34
|
-
"p(90)<1000",
|
|
35
|
-
"p(95)<1500",
|
|
36
|
-
"p(99)<2500",
|
|
37
|
-
],
|
|
38
|
-
"http_req_duration{name:get_project}": [
|
|
39
|
-
"p(90)<1000",
|
|
40
|
-
"p(95)<1500",
|
|
41
|
-
"p(99)<2500",
|
|
42
|
-
],
|
|
43
|
-
"http_req_duration{name:list_projects}": [
|
|
44
|
-
"p(90)<1000",
|
|
45
|
-
"p(95)<1500",
|
|
46
|
-
"p(99)<2500",
|
|
47
|
-
],
|
|
48
|
-
"http_req_duration{name:update_project}": [
|
|
49
|
-
"p(90)<1000",
|
|
50
|
-
"p(95)<1500",
|
|
51
|
-
"p(99)<2500",
|
|
52
|
-
],
|
|
53
|
-
"http_req_duration{name:delete_project}": [
|
|
54
|
-
"p(90)<1000",
|
|
55
|
-
"p(95)<1500",
|
|
56
|
-
"p(99)<2500",
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
run: () => {
|
|
61
|
-
const projectName = generateTestName("load-test-project");
|
|
62
|
-
|
|
63
|
-
group("Projects CRUD", () => {
|
|
64
|
-
// Create
|
|
65
|
-
group("Create Project", () => {
|
|
66
|
-
const createProjectResponse = projectsClient.createProject(
|
|
67
|
-
{
|
|
68
|
-
name: projectName,
|
|
69
|
-
location: "",
|
|
70
|
-
},
|
|
71
|
-
{ tags: { name: "create_project" } },
|
|
72
|
-
);
|
|
73
|
-
const createStatus = createProjectResponse.response.status;
|
|
74
|
-
check(createProjectResponse.response, {
|
|
75
|
-
"create project request successful": (r) =>
|
|
76
|
-
r.status === 200 || r.status === 201,
|
|
77
|
-
});
|
|
78
|
-
// Skip remaining operations if server is down
|
|
79
|
-
if (createStatus === 0 || createStatus === 501) {
|
|
80
|
-
console.log(`Create project request failed: ${createStatus}`);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Read
|
|
86
|
-
group("Get Project", () => {
|
|
87
|
-
const getProjectResponse = projectsClient.getProject(
|
|
88
|
-
projectName,
|
|
89
|
-
undefined,
|
|
90
|
-
{ tags: { name: "get_project" } },
|
|
91
|
-
);
|
|
92
|
-
const getStatus = getProjectResponse.response.status;
|
|
93
|
-
check(getProjectResponse.response, {
|
|
94
|
-
"get project request successful": (r) => r.status === 200,
|
|
95
|
-
});
|
|
96
|
-
if (getStatus === 0 || getStatus === 501) {
|
|
97
|
-
console.log(`Get project request failed: ${getStatus}`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// List
|
|
103
|
-
group("List Projects", () => {
|
|
104
|
-
const listProjectsResponse = projectsClient.listProjects({
|
|
105
|
-
tags: { name: "list_projects" },
|
|
106
|
-
});
|
|
107
|
-
const listStatus = listProjectsResponse.response.status;
|
|
108
|
-
check(listProjectsResponse.response, {
|
|
109
|
-
"list projects request successful": (r) => r.status === 200,
|
|
110
|
-
});
|
|
111
|
-
if (listStatus === 0 || listStatus === 501) {
|
|
112
|
-
console.log(`List projects request failed: ${listStatus}`);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Update
|
|
118
|
-
group("Update Project", () => {
|
|
119
|
-
const updateProjectResponse = projectsClient.updateProject(
|
|
120
|
-
projectName,
|
|
121
|
-
{
|
|
122
|
-
name: projectName,
|
|
123
|
-
readme: "Updated readme for load testing",
|
|
124
|
-
},
|
|
125
|
-
{ tags: { name: "update_project" } },
|
|
126
|
-
);
|
|
127
|
-
const updateStatus = updateProjectResponse.response.status;
|
|
128
|
-
check(updateProjectResponse.response, {
|
|
129
|
-
"update project request successful": (r) => r.status === 200,
|
|
130
|
-
});
|
|
131
|
-
if (updateStatus === 0 || updateStatus === 501) {
|
|
132
|
-
console.log(`Update project request failed: ${updateStatus}`);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Delete
|
|
138
|
-
group("Delete Project", () => {
|
|
139
|
-
const deleteProjectResponse = projectsClient.deleteProject(
|
|
140
|
-
projectName,
|
|
141
|
-
{ tags: { name: "delete_project" } },
|
|
142
|
-
);
|
|
143
|
-
const deleteStatus = deleteProjectResponse.response.status;
|
|
144
|
-
check(deleteProjectResponse.response, {
|
|
145
|
-
"delete project request successful": (r) =>
|
|
146
|
-
r.status === 200 || r.status === 204,
|
|
147
|
-
});
|
|
148
|
-
if (deleteStatus === 0 || deleteStatus === 501) {
|
|
149
|
-
console.log(`Delete project request failed: ${deleteStatus}`);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
sleep(0.1);
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export const options = loadTestProjects.defaultOptions;
|
|
160
|
-
export default loadTestProjects.run;
|