Skip to content

Commit ea83fc1

Browse files
authored
Testing too large file dimension (#3182)
add dimension that expects failure upon a file that is too large
1 parent df38ca6 commit ea83fc1

File tree

3 files changed

+183
-43
lines changed

3 files changed

+183
-43
lines changed

tests/e2e/objects/views/FlinkDatabaseView.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,20 +131,24 @@ export class FlinkDatabaseView extends SearchableView {
131131
* @param electronApp - The Electron application instance
132132
* @param filePath - The path to the JAR file to upload
133133
* @param skipInitiation - If true, skips clicking the upload button (assumes quickpick is already open)
134+
* @param expectSuccess - If true, waits for success notification; if false, caller handles error notification
134135
* @returns The name of the uploaded artifact
135136
*/
136137
async uploadFlinkArtifact(
137138
electronApp: ElectronApplication,
138139
filePath: string,
139140
skipInitiation = false,
141+
expectSuccess = true,
140142
): Promise<string> {
141143
if (!skipInitiation) {
142144
await this.initiateUpload();
143145
}
144146
await this.selectJarFile(electronApp, filePath);
145147
const artifactName = await this.enterArtifactName(filePath);
146148
await this.confirmUpload();
147-
await this.waitForUploadSuccess();
149+
if (expectSuccess) {
150+
await this.waitForUploadSuccess();
151+
}
148152
return artifactName;
149153
}
150154

@@ -306,9 +310,14 @@ export class FlinkDatabaseView extends SearchableView {
306310
* Navigates through the quickpick steps after the upload has been initiated from a JAR file.
307311
* @param artifactName - The name of the uploaded artifact (for verification)
308312
* @param providerRegion - Optional provider/region to match (e.g., "AWS/us-east-2")
313+
* @param expectSuccess - If true, waits for success notification
309314
* @returns The name of the uploaded artifact
310315
*/
311-
async uploadFlinkArtifactFromJAR(artifactName: string, providerRegion?: string): Promise<string> {
316+
async uploadFlinkArtifactFromJAR(
317+
artifactName: string,
318+
providerRegion?: string,
319+
expectSuccess = true,
320+
): Promise<string> {
312321
// Wait for the quickpick to appear
313322
const quickpick = new Quickpick(this.page);
314323
await expect(quickpick.locator).toBeVisible();
@@ -357,12 +366,14 @@ export class FlinkDatabaseView extends SearchableView {
357366
await expect(uploadAction).toBeVisible();
358367
await uploadAction.click();
359368

360-
// Wait for upload success notification
361-
const notificationArea = new NotificationArea(this.page);
362-
const successNotifications = notificationArea.infoNotifications.filter({
363-
hasText: "uploaded successfully",
364-
});
365-
await expect(successNotifications.first()).toBeVisible();
369+
if (expectSuccess) {
370+
// Wait for upload success notification
371+
const notificationArea = new NotificationArea(this.page);
372+
const successNotifications = notificationArea.infoNotifications.filter({
373+
hasText: "uploaded successfully",
374+
});
375+
await expect(successNotifications.first()).toBeVisible();
376+
}
366377

367378
return artifactName;
368379
}

tests/e2e/specs/flinkArtifact.spec.ts

Lines changed: 110 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import type { ElectronApplication, Page } from "@playwright/test";
1+
import type { ElectronApplication, Locator, Page } from "@playwright/test";
22
import { expect } from "@playwright/test";
33
import { stubDialog } from "electron-playwright-helpers";
44
import * as path from "path";
55
import { fileURLToPath } from "url";
66
import { test } from "../baseTest";
77
import { ConnectionType } from "../connectionTypes";
88
import { FileExplorer } from "../objects/FileExplorer";
9+
import { NotificationArea } from "../objects/notifications/NotificationArea";
910
import { Quickpick } from "../objects/quickInputs/Quickpick";
1011
import { FlinkDatabaseView, SelectFlinkDatabase } from "../objects/views/FlinkDatabaseView";
1112
import { ViewItem } from "../objects/views/viewItems/ViewItem";
1213
import { Tag } from "../tags";
1314
import { executeVSCodeCommand } from "../utils/commands";
15+
import { cleanupLargeFile, createLargeFile } from "../utils/flinkDatabase";
1416
import { openConfluentSidebar } from "../utils/sidebarNavigation";
1517
import { randomHexString } from "../utils/strings";
1618

@@ -31,22 +33,33 @@ test.describe("Flink Artifacts", { tag: [Tag.CCloud, Tag.FlinkArtifacts] }, () =
3133
"udfs-simple.jar",
3234
);
3335

36+
const fixturesDir = path.join(__dirname, "..", "..", "fixtures", "flink-artifacts");
37+
38+
const invalidFiles = [
39+
{
40+
description: "oversized artifact (>100MB)",
41+
setupFile: () => createLargeFile({ sizeInMB: 150, directory: fixturesDir }),
42+
cleanupFile: (filePath: string) => cleanupLargeFile(filePath),
43+
shouldSucceed: false,
44+
},
45+
];
46+
3447
const entrypoints = [
3548
{
3649
entrypoint: SelectFlinkDatabase.FromDatabaseViewButton,
37-
testName: "should upload Flink Artifact when cluster selected from Artifacts view button",
50+
testName: "cluster selected from Artifacts view button",
3851
},
3952
{
4053
entrypoint: SelectFlinkDatabase.DatabaseFromResourcesView,
41-
testName: "should upload Flink Artifact when cluster selected from the Resources view",
54+
testName: "cluster selected from the Resources view",
4255
},
4356
{
4457
entrypoint: SelectFlinkDatabase.ComputePoolFromResourcesView,
45-
testName: "should upload Flink Artifact when cluster selected from Flink Compute Pool",
58+
testName: "cluster selected from Flink Compute Pool",
4659
},
4760
{
4861
entrypoint: SelectFlinkDatabase.JarFile,
49-
testName: "should upload Flink Artifact when initiated from JAR file in file explorer",
62+
testName: "initiated from JAR file in file explorer",
5063
},
5164
];
5265

@@ -56,37 +69,73 @@ test.describe("Flink Artifacts", { tag: [Tag.CCloud, Tag.FlinkArtifacts] }, () =
5669
{ provider: "AZURE", region: "eastus" },
5770
];
5871

59-
for (const config of entrypoints) {
60-
for (const providerRegion of providersWithRegions) {
61-
test.describe(`with ${providerRegion.provider}/${providerRegion.region}`, () => {
62-
const { provider, region } = providerRegion;
63-
test(config.testName, async ({ page, electronApp }) => {
64-
await setupTestEnvironment(config.entrypoint, page, electronApp);
65-
const artifactsView = new FlinkDatabaseView(page);
72+
for (const { entrypoint, testName } of entrypoints) {
73+
for (const { provider, region } of providersWithRegions) {
74+
test(`should upload a jar and create an artifact successfully [${provider}/${region}] - ${testName}`, async ({
75+
page,
76+
electronApp,
77+
}) => {
78+
await setupTestEnvironment(entrypoint, page, electronApp);
79+
const artifactsView = new FlinkDatabaseView(page);
6680

67-
await artifactsView.ensureExpanded();
68-
await artifactsView.loadArtifacts(config.entrypoint);
81+
await artifactsView.ensureExpanded();
82+
await artifactsView.loadArtifacts(entrypoint);
83+
const uploadedArtifactName = await startUploadFlow(
84+
entrypoint,
85+
page,
86+
electronApp,
87+
artifactsView,
88+
provider,
89+
region,
90+
artifactPath,
91+
);
6992

70-
const uploadedArtifactName = await startUploadFlow(
71-
config.entrypoint,
93+
const artifactViewItem = await artifactsView.getDatabaseResourceByLabel(
94+
uploadedArtifactName,
95+
artifactsView.artifactsContainer,
96+
);
97+
98+
await expect(artifactViewItem).toBeVisible();
99+
await artifactsView.deleteFlinkArtifact(uploadedArtifactName);
100+
await expect(artifactsView.artifacts.filter({ hasText: uploadedArtifactName })).toHaveCount(
101+
0,
102+
);
103+
});
104+
105+
test(`should fail to upload a jar exceeding the file limit [${provider}/${region}] - ${testName}`, async ({
106+
page,
107+
electronApp,
108+
}) => {
109+
await setupTestEnvironment(entrypoint, page, electronApp);
110+
const artifactsView = new FlinkDatabaseView(page);
111+
112+
await artifactsView.ensureExpanded();
113+
await artifactsView.loadArtifacts(entrypoint);
114+
const initialArtifactCount = await artifactsView.artifacts.count();
115+
const pathToBigArtifact = createLargeFile({ sizeInMB: 150, directory: fixturesDir });
116+
try {
117+
await startUploadFlow(
118+
entrypoint,
72119
page,
73120
electronApp,
74121
artifactsView,
75122
provider,
76123
region,
124+
pathToBigArtifact,
125+
false, // expectSuccess - we expect this upload to fail
77126
);
127+
} catch (error) {
128+
// Swallow any errors from the upload flow since we expect failure
129+
}
78130

79-
const artifactViewItem = await artifactsView.getDatabaseResourceByLabel(
80-
uploadedArtifactName,
81-
artifactsView.artifactsContainer,
82-
);
131+
await expect(artifactsView.artifacts).toHaveCount(initialArtifactCount);
83132

84-
await expect(artifactViewItem).toBeVisible();
85-
await artifactsView.deleteFlinkArtifact(uploadedArtifactName);
86-
await expect(
87-
artifactsView.artifacts.filter({ hasText: uploadedArtifactName }),
88-
).toHaveCount(0);
133+
const notificationArea = new NotificationArea(page);
134+
const failureNotifications: Locator = notificationArea.errorNotifications.filter({
135+
hasText: /Failed to upload/,
89136
});
137+
await expect(failureNotifications.first()).toBeVisible();
138+
cleanupLargeFile(pathToBigArtifact);
90139
});
91140
}
92141
}
@@ -98,8 +147,6 @@ test.describe("Flink Artifacts", { tag: [Tag.CCloud, Tag.FlinkArtifacts] }, () =
98147
): Promise<void> {
99148
// JAR file test requires opening the fixtures folder as a workspace
100149
if (entrypoint === SelectFlinkDatabase.JarFile) {
101-
const fixturesDir = path.join(__dirname, "..", "..", "fixtures", "flink-artifacts");
102-
103150
await stubDialog(electronApp, "showOpenDialog", {
104151
filePaths: [fixturesDir],
105152
});
@@ -121,21 +168,41 @@ test.describe("Flink Artifacts", { tag: [Tag.CCloud, Tag.FlinkArtifacts] }, () =
121168
artifactsView: FlinkDatabaseView,
122169
provider: string,
123170
region: string,
171+
filePath: string,
172+
expectSuccess = true,
124173
): Promise<string> {
125174
switch (entrypoint) {
126175
case SelectFlinkDatabase.DatabaseFromResourcesView:
127-
return await completeArtifactUploadFlow(electronApp, artifactPath, artifactsView);
176+
return await completeArtifactUploadFlow(
177+
electronApp,
178+
filePath,
179+
artifactsView,
180+
expectSuccess,
181+
);
128182
case SelectFlinkDatabase.FromDatabaseViewButton:
129-
return await completeArtifactUploadFlow(electronApp, artifactPath, artifactsView);
183+
return await completeArtifactUploadFlow(
184+
electronApp,
185+
filePath,
186+
artifactsView,
187+
expectSuccess,
188+
);
130189
case SelectFlinkDatabase.ComputePoolFromResourcesView:
131-
return await completeUploadFlowForComputePool(electronApp, artifactsView, provider, region);
190+
return await completeUploadFlowForComputePool(
191+
electronApp,
192+
artifactsView,
193+
provider,
194+
region,
195+
filePath,
196+
expectSuccess,
197+
);
132198
case SelectFlinkDatabase.JarFile:
133199
return await completeArtifactUploadFlowForJAR(
134200
page,
135-
artifactPath,
201+
filePath,
136202
artifactsView,
137203
provider,
138204
region,
205+
expectSuccess,
139206
);
140207
}
141208
}
@@ -145,13 +212,16 @@ test.describe("Flink Artifacts", { tag: [Tag.CCloud, Tag.FlinkArtifacts] }, () =
145212
artifactsView: FlinkDatabaseView,
146213
provider: string,
147214
region: string,
215+
filePath: string,
216+
expectSuccess = true,
148217
): Promise<string> {
149218
await artifactsView.clickUploadFromComputePool(provider, region);
150219
// Skip initiation since the upload modal was already opened via the compute pool context menu
151220
const uploadedArtifactName = await artifactsView.uploadFlinkArtifact(
152221
electronApp,
153-
artifactPath,
222+
filePath,
154223
true,
224+
expectSuccess,
155225
);
156226

157227
await artifactsView.selectKafkaClusterByProviderRegion(provider, region);
@@ -165,9 +235,9 @@ async function completeArtifactUploadFlow(
165235
electronApp: ElectronApplication,
166236
artifactPath: string,
167237
artifactsView: FlinkDatabaseView,
238+
expectSuccess = true,
168239
): Promise<string> {
169-
const uploadedArtifactName = await artifactsView.uploadFlinkArtifact(electronApp, artifactPath);
170-
return uploadedArtifactName;
240+
return await artifactsView.uploadFlinkArtifact(electronApp, artifactPath, false, expectSuccess);
171241
}
172242

173243
/**
@@ -180,6 +250,7 @@ async function completeArtifactUploadFlowForJAR(
180250
artifactsView: FlinkDatabaseView,
181251
provider: string,
182252
region: string,
253+
expectSuccess = true,
183254
): Promise<string> {
184255
// Use the artifact file name (without extension) as the artifact name
185256
const baseFileName = path.basename(artifactPath, ".jar");
@@ -194,7 +265,11 @@ async function completeArtifactUploadFlowForJAR(
194265
const fileItem = new ViewItem(page, jarFile);
195266
await fileItem.rightClickContextMenuAction("Upload Flink Artifact to Confluent Cloud");
196267

197-
await artifactsView.uploadFlinkArtifactFromJAR(artifactName, `${provider}/${region}`);
268+
await artifactsView.uploadFlinkArtifactFromJAR(
269+
artifactName,
270+
`${provider}/${region}`,
271+
expectSuccess,
272+
);
198273

199274
// Switch back to the Confluent extension sidebar from the file explorer
200275
await openConfluentSidebar(page);

tests/e2e/utils/flinkDatabase.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as fs from "fs";
2+
import * as os from "os";
3+
import * as path from "path";
4+
5+
export interface LargeFileOptions {
6+
/** Size in megabytes. Default is 101MB (just over the 100MB threshold) */
7+
sizeInMB?: number;
8+
/** Custom filename. If not provided, a default name will be used */
9+
filename?: string;
10+
/** Directory to create the file in. Defaults to system temp directory */
11+
directory?: string;
12+
}
13+
14+
/**
15+
* Creates a large file for testing artifact upload rejection.
16+
* The file is filled with random data to simulate a real artifact.
17+
*
18+
* @param options - Configuration options for the large file
19+
* @returns The absolute path to the created file
20+
*/
21+
export function createLargeFile(options: LargeFileOptions = {}): string {
22+
const {
23+
sizeInMB = 101,
24+
filename = `test-large-artifact-${Date.now()}.jar`,
25+
directory = os.tmpdir(),
26+
} = options;
27+
28+
const filePath = path.join(directory, filename);
29+
const sizeInBytes = sizeInMB * 1024 * 1024;
30+
const chunkSize = 1024 * 1024; // 1MB chunks for efficient writing
31+
32+
const buffer = Buffer.alloc(chunkSize, 0); // Fill with zeros (or any byte)
33+
34+
const fd = fs.openSync(filePath, "w");
35+
try {
36+
for (let written = 0; written < sizeInBytes; written += chunkSize) {
37+
const bytesToWrite = Math.min(chunkSize, sizeInBytes - written);
38+
fs.writeSync(fd, buffer, 0, bytesToWrite);
39+
}
40+
} finally {
41+
fs.closeSync(fd); // Always close even if error occurs
42+
}
43+
return filePath;
44+
}
45+
46+
/**
47+
* Cleans up (deletes) a large test file.
48+
* @param filePath - The absolute path to the file to delete
49+
*/
50+
export function cleanupLargeFile(filePath: string): void {
51+
if (fs.existsSync(filePath)) {
52+
fs.unlinkSync(filePath);
53+
}
54+
}

0 commit comments

Comments
 (0)