Skip to content

Commit 8fefdf4

Browse files
committed
test
Signed-off-by: Gustavo Lira <[email protected]>
1 parent 76bbff0 commit 8fefdf4

File tree

2 files changed

+153
-64
lines changed

2 files changed

+153
-64
lines changed

e2e-tests/playwright/e2e/audit-log/catalog.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { UIhelper } from "../../utils/ui-helper";
44
import { LogUtils } from "./log-utils";
55
import { CatalogImport } from "../../support/pages/catalog-import";
66

7-
test.describe.skip("Audit Log check for Catalog Plugin", () => {
7+
test.describe("Audit Log check for Catalog Plugin", () => {
88
let uiHelper: UIhelper;
99
let common: Common;
1010
let catalogImport: CatalogImport;

e2e-tests/playwright/e2e/audit-log/log-utils.ts

Lines changed: 152 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@ import { execFile } from "child_process";
33
import { Log } from "./logs";
44

55
export class LogUtils {
6+
/**
7+
* Executes a command and returns the output as a promise.
8+
*
9+
* @param command The command to execute
10+
* @param args An array of arguments for the command
11+
* @returns A promise that resolves with the command output
12+
*/
13+
static executeCommand(command: string, args: string[] = []): Promise<string> {
14+
return new Promise((resolve, reject) => {
15+
execFile(command, args, { encoding: "utf8" }, (error, stdout, stderr) => {
16+
if (error) {
17+
console.error("Error executing command:", error);
18+
reject(`Error: ${error.message}`);
19+
return;
20+
}
21+
if (stderr) {
22+
console.warn("stderr warning:", stderr);
23+
}
24+
resolve(stdout);
25+
});
26+
});
27+
}
28+
629
/**
730
* Validates if the actual log matches the expected log values.
831
* It compares both primitive and nested object properties.
@@ -11,7 +34,6 @@ export class LogUtils {
1134
* @param expected The expected log values to validate against
1235
*/
1336
public static validateLog(actual: Log, expected: Partial<Log>) {
14-
// Loop through each key in the expected log object
1537
Object.keys(expected).forEach((key) => {
1638
const expectedValue = expected[key as keyof Log];
1739
const actualValue = actual[key as keyof Log];
@@ -42,71 +64,116 @@ export class LogUtils {
4264
}
4365

4466
/**
45-
* Executes a command and returns the output as a promise.
67+
* Lists all pods in the specified namespace and returns their details.
4668
*
47-
* @param command The command to execute
48-
* @param args An array of arguments for the command
49-
* @returns A promise that resolves with the command output
69+
* @param namespace The namespace to list pods from
70+
* @returns A promise that resolves with the pod details
5071
*/
51-
static executeCommand(command: string, args: string[] = []): Promise<string> {
52-
return new Promise((resolve, reject) => {
53-
execFile(command, args, { encoding: "utf8" }, (error, stdout, stderr) => {
54-
if (error) {
55-
console.error("Error executing command:", error);
56-
reject(`Error: ${error.message}`);
57-
return;
58-
}
59-
if (stderr) {
60-
console.warn("stderr warning:", stderr);
61-
}
62-
resolve(stdout);
63-
});
64-
});
72+
static async listPods(namespace: string): Promise<string> {
73+
const args = ["get", "pods", "-n", namespace, "-o", "wide"];
74+
try {
75+
console.log("Fetching pod list with command:", "oc", args.join(" "));
76+
return await LogUtils.executeCommand("oc", args);
77+
} catch (error) {
78+
console.error("Error listing pods:", error);
79+
throw new Error(
80+
`Failed to list pods in namespace "${namespace}": ${error}`,
81+
);
82+
}
6583
}
6684

6785
/**
68-
* Fetches the logs from pods that match the fixed pod selector and applies a grep filter.
69-
* The pod selector is:
70-
* - app.kubernetes.io/component=backstage
71-
* - app.kubernetes.io/instance=redhat-developer-hub
72-
* - app.kubernetes.io/name=developer-hub
86+
* Fetches detailed information about a specific pod.
87+
*
88+
* @param podName The name of the pod to fetch details for
89+
* @param namespace The namespace where the pod is located
90+
* @returns A promise that resolves with the pod details in JSON format
91+
*/
92+
static async getPodDetails(
93+
podName: string,
94+
namespace: string,
95+
): Promise<string> {
96+
const args = ["get", "pod", podName, "-n", namespace, "-o", "json"];
97+
try {
98+
const output = await LogUtils.executeCommand("oc", args);
99+
console.log(`Details for pod ${podName}:`, output);
100+
return output;
101+
} catch (error) {
102+
console.error(`Error fetching details for pod ${podName}:`, error);
103+
throw new Error(`Failed to fetch pod details: ${error}`);
104+
}
105+
}
106+
107+
/**
108+
* Fetches logs with retry logic in case the log is not immediately available.
73109
*
74110
* @param filter The string to filter the logs
75-
* @returns A promise that resolves with the filtered logs
111+
* @param maxRetries Maximum number of retry attempts
112+
* @param retryDelay Delay (in milliseconds) between retries
113+
* @returns The log line matching the filter, or throws an error if not found
76114
*/
77-
static async getPodLogsWithGrep(filter: string): Promise<string> {
115+
static async getPodLogsWithRetry(
116+
filter: string,
117+
maxRetries: number = 3,
118+
retryDelay: number = 5000,
119+
): Promise<string> {
78120
const podSelector =
79121
"app.kubernetes.io/component=backstage,app.kubernetes.io/instance=rhdh,app.kubernetes.io/name=backstage";
80122
const tailNumber = 30;
81-
const namespace = process.env.NAME_SPACE || "default";
123+
const namespace = process.env.NAME_SPACE || "showcase-ci-nightly";
82124

83-
const args = [
84-
"logs",
85-
"-l",
86-
podSelector,
87-
`--tail=${tailNumber}`,
88-
"-c",
89-
"backstage-backend",
90-
"-n",
91-
namespace,
92-
];
125+
let attempt = 0;
126+
while (attempt <= maxRetries) {
127+
try {
128+
console.log(
129+
`Attempt ${attempt + 1}/${maxRetries + 1}: Fetching logs...`,
130+
);
131+
const args = [
132+
"logs",
133+
"-l",
134+
podSelector,
135+
`--tail=${tailNumber}`,
136+
"-c",
137+
"backstage-backend",
138+
"-n",
139+
namespace,
140+
];
93141

94-
console.log("Executing command:", "oc", args.join(" "));
142+
console.log("Executing command:", "oc", args.join(" "));
143+
const output = await LogUtils.executeCommand("oc", args);
95144

96-
try {
97-
const output = await LogUtils.executeCommand("oc", args);
145+
console.log("Raw log output:", output);
98146

99-
const logLines = output.split("\n");
147+
const logLines = output.split("\n");
148+
const filteredLines = logLines.filter((line) => line.includes(filter));
100149

101-
const filteredLines = logLines.filter((line) => line.includes(filter));
150+
if (filteredLines.length > 0) {
151+
console.log("Matching log line found:", filteredLines[0]);
152+
return filteredLines[0]; // Return the first matching log
153+
}
102154

103-
const firstMatch = filteredLines[0] || "";
155+
console.warn(
156+
`No matching logs found for filter "${filter}" on attempt ${
157+
attempt + 1
158+
}. Retrying...`,
159+
);
160+
} catch (error) {
161+
console.error(
162+
`Error fetching logs on attempt ${attempt + 1}:`,
163+
error.message,
164+
);
165+
}
104166

105-
return firstMatch;
106-
} catch (error) {
107-
console.error("Error fetching logs:", error);
108-
throw new Error(`Failed to fetch logs: ${error}`);
167+
attempt++;
168+
if (attempt <= maxRetries) {
169+
console.log(`Waiting ${retryDelay / 1000} seconds before retrying...`);
170+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
171+
}
109172
}
173+
174+
throw new Error(
175+
`Failed to fetch logs for filter "${filter}" after ${maxRetries + 1} attempts.`,
176+
);
110177
}
111178

112179
/**
@@ -115,8 +182,8 @@ export class LogUtils {
115182
* @returns A promise that resolves when the login is successful
116183
*/
117184
static async loginToOpenShift(): Promise<void> {
118-
const token = process.env.K8S_CLUSTER_TOKEN;
119-
const server = process.env.K8S_CLUSTER_URL;
185+
const token = process.env.K8S_CLUSTER_TOKEN || "";
186+
const server = process.env.K8S_CLUSTER_URL || "";
120187

121188
if (!token || !server) {
122189
throw new Error(
@@ -155,19 +222,41 @@ export class LogUtils {
155222
baseURL: string,
156223
plugin: string,
157224
) {
158-
const actualLog = await LogUtils.getPodLogsWithGrep(eventName);
159-
const expectedLog: Partial<Log> = {
160-
actor: {
161-
hostname: new URL(baseURL).hostname,
162-
},
163-
message,
164-
plugin,
165-
request: {
166-
method,
167-
url,
168-
},
169-
};
170-
console.log(actualLog);
171-
LogUtils.validateLog(JSON.parse(actualLog), expectedLog);
225+
try {
226+
const actualLog = await LogUtils.getPodLogsWithRetry(eventName);
227+
console.log("Raw log output before filtering:", actualLog);
228+
229+
let parsedLog: Log;
230+
try {
231+
parsedLog = JSON.parse(actualLog);
232+
} catch (parseError) {
233+
console.error("Failed to parse log JSON. Log content:", actualLog);
234+
throw new Error(`Invalid JSON received for log: ${parseError}`);
235+
}
236+
237+
const expectedLog: Partial<Log> = {
238+
actor: {
239+
hostname: new URL(baseURL).hostname,
240+
},
241+
message,
242+
plugin,
243+
request: {
244+
method,
245+
url,
246+
},
247+
};
248+
249+
console.log("Validating log with expected values:", expectedLog);
250+
LogUtils.validateLog(parsedLog, expectedLog);
251+
} catch (error) {
252+
console.error("Error validating log event:", error);
253+
console.error("Event name:", eventName);
254+
console.error("Expected message:", message);
255+
console.error("Expected method:", method);
256+
console.error("Expected URL:", url);
257+
console.error("Base URL:", baseURL);
258+
console.error("Plugin:", plugin);
259+
throw error;
260+
}
172261
}
173262
}

0 commit comments

Comments
 (0)