Skip to content

Commit d98d536

Browse files
committed
RHTAP-2547 Add import test
RHTAP-2547 Add import test
1 parent 62bead1 commit d98d536

File tree

13 files changed

+1412
-3
lines changed

13 files changed

+1412
-3
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,8 @@ TESTPLAN_PATH=./custom-testplan.json TESTPLAN_NAME=backend-tests npm test
335335

336336
# Force UI test execution
337337
ENABLE_UI_TESTS=true npm test
338+
# Run import template tests (supports all Git providers)
339+
npm run test:import
338340

339341
# View test report
340342
npm run test:report
@@ -387,6 +389,10 @@ TESTPLAN_NAME=github-tests npm test
387389
# Run UI tests
388390
npm run test:ui
389391

392+
# Run import template tests (supports all Git providers)
393+
npm run test:import
394+
395+
390396
# View test report
391397
npm run test:report
392398

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"generate-config": "npx ts-node scripts/generateProjectConfig.ts",
1313
"test": "npm run generate-config && playwright test",
1414
"test:e2e": "TESTPLAN_NAME=backend-tests npm run generate-config && playwright test",
15+
"test:import": "TESTPLAN_NAME=import-tests npm run generate-config && playwright test",
1516
"test:ui": "TESTPLAN_NAME=ui-tests playwright test",
1617
"test:ui-interactive": "TESTPLAN_NAME=ui-tests playwright test --ui",
1718
"test:all": "TESTPLAN_NAME=backend-tests npm run generate-config && playwright test && TESTPLAN_NAME=ui-tests playwright test",

playwright.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ declare module '@playwright/test' {
1515
// Configuration constants
1616
const DEFAULT_TIMEOUT = 2100000; // 35 minutes
1717

18-
// Environment variable flags to control which tests run
1918
const DEFAULT_WORKERS = 6;
2019
const DEFAULT_UI_TIMEOUT = 60000;
2120

@@ -83,6 +82,7 @@ try {
8382

8483
let e2eProjects: any[] = [];
8584
let uiProjects: any[] = [];
85+
let importProjects: any[] = [];
8686
let authProjects: any[] = [];
8787

8888
// Create E2E projects (always created for backend tests)
@@ -187,7 +187,8 @@ try {
187187
allProjects = [
188188
...authProjects,
189189
...e2eProjects,
190-
...uiProjects
190+
...uiProjects,
191+
...importProjects
191192
];
192193

193194
} catch (error) {

src/api/bitbucket/services/bitbucket-repository.service.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import retry from 'async-retry';
2+
import { defaultLogger } from '../../../log/logger';
13
import { BitbucketHttpClient } from '../http/bitbucket-http.client';
2-
import { BitbucketRepository, BitbucketBranch, BitbucketCommit, BitbucketPaginatedResponse } from '../types/bitbucket.types';
4+
import { BitbucketRepository, BitbucketBranch, BitbucketCommit, BitbucketPaginatedResponse, BitbucketDirectoryEntry } from '../types/bitbucket.types';
35

46
export class BitbucketRepositoryService {
57
constructor(private readonly httpClient: BitbucketHttpClient) {}
@@ -47,4 +49,122 @@ export class BitbucketRepositoryService {
4749
public async getFileContent(workspace: string, repoSlug: string, filePath: string, ref: string = 'main'): Promise<string> {
4850
return this.httpClient.get<string>(`/repositories/${workspace}/${repoSlug}/src/${ref}/${filePath}`);
4951
}
52+
53+
public async getDirectoryContent(workspace: string, repoSlug: string, path: string, ref: string = 'main'): Promise<BitbucketDirectoryEntry[]> {
54+
// Input validation
55+
if (!workspace || workspace.trim() === '') {
56+
throw new Error('Workspace is required and cannot be empty');
57+
}
58+
if (!repoSlug || repoSlug.trim() === '') {
59+
throw new Error('Repository slug is required and cannot be empty');
60+
}
61+
if (!ref || ref.trim() === '') {
62+
throw new Error('Reference is required and cannot be empty');
63+
}
64+
65+
const trimmedWorkspace = workspace.trim();
66+
const trimmedRepoSlug = repoSlug.trim();
67+
const trimmedPath = path.trim();
68+
const trimmedRef = ref.trim();
69+
70+
try {
71+
const response = await retry(
72+
async () => {
73+
return await this.httpClient.get<BitbucketPaginatedResponse<BitbucketDirectoryEntry>>(
74+
`/repositories/${trimmedWorkspace}/${trimmedRepoSlug}/src/${trimmedRef}/${trimmedPath}`
75+
);
76+
},
77+
{
78+
retries: 3,
79+
minTimeout: 1000,
80+
maxTimeout: 5000,
81+
factor: 2,
82+
onRetry: (error: Error, attempt: number) => {
83+
defaultLogger.warn({
84+
operation: 'getDirectoryContent',
85+
workspace: trimmedWorkspace,
86+
repoSlug: trimmedRepoSlug,
87+
path: trimmedPath,
88+
ref: trimmedRef,
89+
attempt,
90+
error: error.message
91+
}, `Retrying directory content retrieval (attempt ${attempt}/3)`);
92+
}
93+
}
94+
);
95+
96+
// Defensive validation of response
97+
if (!response || !Array.isArray(response.values)) {
98+
defaultLogger.warn({
99+
operation: 'getDirectoryContent',
100+
workspace: trimmedWorkspace,
101+
repoSlug: trimmedRepoSlug,
102+
path: trimmedPath,
103+
ref: trimmedRef,
104+
responseType: typeof response,
105+
hasValues: !!response?.values
106+
}, `Invalid response structure for directory content, returning empty array`);
107+
return [];
108+
}
109+
110+
defaultLogger.info({
111+
operation: 'getDirectoryContent',
112+
workspace: trimmedWorkspace,
113+
repoSlug: trimmedRepoSlug,
114+
path: trimmedPath,
115+
ref: trimmedRef,
116+
itemCount: response.values.length
117+
}, `Successfully retrieved directory content for ${trimmedWorkspace}/${trimmedRepoSlug}/${trimmedPath}`);
118+
119+
return response.values;
120+
} catch (error: any) {
121+
// Handle 404 errors gracefully for idempotent operations
122+
if (error.response?.status === 404 || error.status === 404 || error.message?.includes('404')) {
123+
defaultLogger.info({
124+
operation: 'getDirectoryContent',
125+
workspace: trimmedWorkspace,
126+
repoSlug: trimmedRepoSlug,
127+
path: trimmedPath,
128+
ref: trimmedRef,
129+
status: 'not_found'
130+
}, `Directory content not found for ${trimmedWorkspace}/${trimmedRepoSlug}/${trimmedPath} (404 Not Found)`);
131+
return [];
132+
}
133+
134+
defaultLogger.error({
135+
operation: 'getDirectoryContent',
136+
workspace: trimmedWorkspace,
137+
repoSlug: trimmedRepoSlug,
138+
path: trimmedPath,
139+
ref: trimmedRef,
140+
error: error.message,
141+
status: error.response?.status || error.status
142+
}, `Failed to get directory content for ${trimmedWorkspace}/${trimmedRepoSlug}/${trimmedPath}`);
143+
throw error;
144+
}
145+
}
146+
147+
public async deleteFile(workspace: string, repoSlug: string, filePath: string, branch: string = 'main', commitMessage: string = 'Delete file'): Promise<void> {
148+
try {
149+
// Bitbucket API requires a commit with file deletion
150+
const commitData = {
151+
message: commitMessage,
152+
branch: branch,
153+
files: {
154+
[filePath]: null // null value indicates file deletion
155+
}
156+
};
157+
158+
await this.httpClient.post(`/repositories/${workspace}/${repoSlug}/src`, commitData, {
159+
headers: {
160+
'Content-Type': 'application/x-www-form-urlencoded',
161+
},
162+
});
163+
164+
console.log(`Successfully deleted file ${filePath} from ${workspace}/${repoSlug}`);
165+
} catch (error) {
166+
console.error(`Failed to delete file ${filePath} from ${workspace}/${repoSlug}:`, error);
167+
throw error;
168+
}
169+
}
50170
}

src/api/bitbucket/types/bitbucket.types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ export interface BitbucketWebhook {
8585
events: string[];
8686
}
8787

88+
export interface BitbucketDirectoryEntry {
89+
path: string;
90+
type: 'commit_file' | 'commit_directory';
91+
size?: number;
92+
commit?: {
93+
hash: string;
94+
};
95+
}
96+
8897
export interface BitbucketPaginatedResponse<T> {
8998
values: T[];
9099
page?: number;

0 commit comments

Comments
 (0)