Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cypress.config.ts
6 changes: 5 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ jobs:
echo Create cypress.config.ts from example
cp cypress.config.ts.example cypress.config.ts
yarn install
yarn run cypress run --config excludeSpecPattern=cypress/e2e/filesearch/Filesearch.spec.ts --record --key ${{ secrets.CYPRESS_DASHBOARD_KEY }}
yarn run cypress run \
--config '{"excludeSpecPattern":["cypress/e2e/filesearch/Filesearch.spec.ts","cypress/e2e/smoke.spec.ts"]}' \
--record \
--key ${{ secrets.CYPRESS_DASHBOARD_KEY }}

env:
SPLIT: 2
SPLIT_INDEX: ${{ matrix.shard }}
Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Whatsapp Web JS - Flow Smoke Test

permissions:
contents: read

on:
schedule:
- cron: '*/30 * * * *'
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'production'
type: choice
options:
- production
- staging

concurrency:
group: whatsapp-smoke-${{ github.event.inputs.environment || 'production' }}
cancel-in-progress: false

jobs:
wwebjs-smoke:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Chromium dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y \
libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2

- name: Install dependencies
run: yarn install

- name: Write GCP credentials
run: echo '${{ secrets.GCP_SA_KEY_JSON }}' > /tmp/gcp-key.json
Comment on lines +47 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set restrictive permissions on the credentials file.

Writing the GCP service account key to a file without explicit permissions could expose it to other processes. While GitHub Actions runners are isolated, it's best practice to restrict file permissions.

🔒 Proposed fix
       - name: Write GCP credentials
-        run: echo '${{ secrets.GCP_SA_KEY_JSON }}' > /tmp/gcp-key.json
+        run: |
+          echo '${{ secrets.GCP_SA_KEY_JSON }}' > /tmp/gcp-key.json
+          chmod 600 /tmp/gcp-key.json
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Write GCP credentials
run: echo '${{ secrets.GCP_SA_KEY_JSON }}' > /tmp/gcp-key.json
- name: Write GCP credentials
run: |
echo '${{ secrets.GCP_SA_KEY_JSON }}' > /tmp/gcp-key.json
chmod 600 /tmp/gcp-key.json
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/smoke.yml around lines 47 - 48, The "Write GCP
credentials" step writes the service account JSON to /tmp/gcp-key.json with no
explicit permissions; update the step so the file is created with restrictive
permissions (owner read/write only) by e.g. setting a restrictive umask before
writing or immediately running chmod 600 on /tmp/gcp-key.json after the echo,
ensuring the credentials file is only readable by the runner process.


- name: Run WhatsApp flow test
env:
WHATSAPP_TARGET_ENV: ${{ github.event.inputs.environment || 'production' }}
GCS_BUCKET: ${{ secrets.GCS_BUCKET }}
GCS_KEY_FILE: /tmp/gcp-key.json
INSTATUS_API_KEY: ${{ secrets.INSTATUS_API_KEY }}
INSTATUS_PAGE_ID: ${{ secrets.INSTATUS_PAGE_ID }}
INSTATUS_COMPONENT_ID: ${{ secrets.INSTATUS_COMPONENT_ID }}
BOT_PHONE_NUMBER: ${{ secrets.BOT_PHONE_NUMBER }}
run: yarn wa:smoke

- name: Clean up credentials
if: always()
run: rm -f /tmp/gcp-key.json
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# misc
.DS_Store
.env
.envrc

npm-debug.log*
yarn-debug.log*
Expand All @@ -23,4 +24,8 @@ package-lock.json

cypress.config.ts
cypress/screenshots
cypress/videos
cypress/videos

# wwebjs
.wwebjs_auth
.wwebjs_cache
79 changes: 79 additions & 0 deletions cypress/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
describe('smoke test', () => {
let testPassed = true;

afterEach(function () {
if (this.currentTest?.state === 'failed') {
testPassed = false;
}
});

after(() => {
cy.env(['INSTATUS_API_KEY', 'INSTATUS_PAGE_ID', 'INSTATUS_COMPONENT_ID']).then(
({ INSTATUS_API_KEY, INSTATUS_PAGE_ID, INSTATUS_COMPONENT_ID }) => {
const status = testPassed ? 'OPERATIONAL' : 'MAJOROUTAGE';
cy.request({
method: 'PUT',
url: `https://api.instatus.com/v2/${INSTATUS_PAGE_ID}/components/${INSTATUS_COMPONENT_ID}`,
headers: { Authorization: `Bearer ${INSTATUS_API_KEY}` },
body: { status },
failOnStatusCode: false,
});
}
);
});

it('passes', () => {
cy.env([
'SMOKE_TEST_CHAT_URL',
'SMOKE_TEST_LOGIN_PHONE_NUMBER',
'SMOKE_TEST_LOGIN_PASSWORD',
]).then(({ SMOKE_TEST_CHAT_URL, SMOKE_TEST_LOGIN_PHONE_NUMBER, SMOKE_TEST_LOGIN_PASSWORD }) => {
cy.visit(SMOKE_TEST_CHAT_URL);
cy.get('[data-testid="phoneInput"] [name="phoneNumber"]').click();
cy.get('[data-testid="phoneInput"] [name="phoneNumber"]').type(SMOKE_TEST_LOGIN_PHONE_NUMBER);
cy.get('[data-testid="outlinedInput"] [name="password"]').click();
cy.get('[data-testid="outlinedInput"] [name="password"]').click();
Comment on lines +34 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove redundant click.

The password field is clicked twice before typing. Only one click is needed to focus the field.

🔧 Proposed fix
-      cy.get('[data-testid="outlinedInput"] [name="password"]').click();
       cy.get('[data-testid="outlinedInput"] [name="password"]').click();
       cy.get('[data-testid="outlinedInput"] [name="password"]').type(SMOKE_TEST_LOGIN_PASSWORD);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cypress/e2e/smoke.spec.ts` around lines 34 - 35, Remove the redundant second
click on the password field: in the test where
cy.get('[data-testid="outlinedInput"] [name="password"]').click() is called
twice, keep a single cy.get(...).click() before typing into the field (remove
the duplicate call referencing the same selector).

cy.get('[data-testid="outlinedInput"] [name="password"]').type(SMOKE_TEST_LOGIN_PASSWORD);
cy.get('[data-testid="SubmitButton"]').click();
cy.get('[data-testid="dropdownIcon"]', { timeout: 10000 }).should('be.visible').click();
cy.get('[data-testid="flowButton"]', { timeout: 10000 }).should('be.visible').click();

cy.get('div[data-testid="AutocompleteInput"]', { timeout: 10000 })
.should('be.visible')
.within(() => {
cy.get('input').type('smoke-test');
});
cy.get('ul.MuiAutocomplete-listbox')
.first()
.within(() => {
cy.get('li').first().click();
});

cy.get('[data-testid="ok-button"]').click();

// Wait for 30 seconds to ensure all messages are in
cy.wait(30000);

cy.get('[data-testid="messageContainer"] [data-testid="content"]', { timeout: 10000 }).then(
($messages) => {
expect($messages.length).to.be.at.least(3);
const lastThree = $messages.slice(-3);

// First of last three should have the date message
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const dateString = `World! ${day}/${month}/${year}`;
expect(Cypress.$(lastThree[0]).text()).to.contain(dateString);
Comment on lines +63 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Date assertion may cause flaky failures.

The test constructs the expected date string from new Date() at test execution time and assumes the WhatsApp message contains the same date. This creates timing issues:

  • Tests running near midnight may fail if the message timestamp crosses the day boundary
  • Timezone differences between the test runner and the WhatsApp bot could cause mismatches
  • Message delays could result in different dates

Consider either:

  1. Accepting a date within a reasonable range (today ± 1 day)
  2. Using a regex pattern to validate date format rather than exact date
  3. Making the flow send a known static value instead of a dynamic date
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cypress/e2e/smoke.spec.ts` around lines 63 - 68, The current assertion builds
a strict expected string in dateString and checks Cypress.$(lastThree[0]).text()
for an exact match, which is flaky around midnights/timezones; update the
assertion in the test (where dateString and lastThree are used) to be tolerant:
either (a) generate a small set of allowed date strings (today, yesterday,
tomorrow) and assert the WhatsApp message contains any of those, or (b) replace
the exact match with a regex that validates the DD/MM/YYYY format (e.g.,
\d{2}\/\d{2}\/\d{4}) and only assert format, not exact value; apply this change
where dateString is defined and where expect(...).to.contain(...) is called.


// Second should have 'elephant'
expect(Cypress.$(lastThree[1]).text()).to.contain('elephant');

// Third should have an audio element inside
expect(Cypress.$(lastThree[2]).find('audio').length).to.be.greaterThan(0);
}
);
});
});
});
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
"repository": "git@github.com:glific/cypress-testing.git",
"author": "Kurund Jalmi <kurund.jalmi@webaccessglobal.com>",
"dependencies": {
"cypress": "13.6.2",
"@google-cloud/storage": "^7.14.0",
"cypress": "^15.15.0",
"cypress-split": "^1.24.28",
"dayjs": "^1.11.10",
"typescript": "^5.7.2"
"qrcode-terminal": "^0.12.0",
"tsx": "^4.19.4",
"typescript": "^5.7.2",
"whatsapp-web.js": "^1.26.1"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/qrcode-terminal": "^0.12.2",
"prettier": "^2.8.2"
},
"scripts": {
"cy:run": "cypress run"
"cy:run": "cypress run",
"wa:smoke": "tsx wwebjs/smoke/runner.ts"
}
}
62 changes: 62 additions & 0 deletions wwebjs/smoke/flows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export interface ExpectedMessage {
containsText?: string;
exactText?: string;
hasMedia?: boolean;
mediaType?: 'image' | 'audio' | 'video' | 'document' | 'sticker';
}

export interface FlowDefinition {
id: string;
description: string;
triggerMessage: string;
expectedResponses: ExpectedMessage[];
timeoutMs?: number;
}

export interface TargetEnvironment {
name: string;
botPhoneNumber: string;
sessionId: string;
gcsBucket: string;
instatus: {
pageId: string;
componentId: string;
};
}

export const FLOWS: Record<string, FlowDefinition> = {
'smoke-test': {
id: 'smoke-test',
description: 'Verify core flow: date message → elephant text → audio',
triggerMessage: 'smoke',
expectedResponses: [
{ containsText: 'World!' },
{ containsText: 'elephant' },
{ hasMedia: true, mediaType: 'audio' },
],
timeoutMs: 120_000,
},
};

export const ENVIRONMENTS: Record<string, TargetEnvironment> = {
production: {
name: 'production',
botPhoneNumber: process.env.BOT_PHONE_NUMBER ?? '+918657048982',
sessionId: process.env.WHATSAPP_SESSION_ID ?? 'production-sender',
Comment on lines +44 to +45
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the hard-coded production phone fallback.

If BOT_PHONE_NUMBER is not set, the run still targets a real number by default. With the current production default selection, that can send smoke traffic to an unintended recipient instead of failing fast.

Suggested fix
-    botPhoneNumber: process.env.BOT_PHONE_NUMBER ?? '+918657048982',
+    botPhoneNumber: process.env.BOT_PHONE_NUMBER ?? '',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
botPhoneNumber: process.env.BOT_PHONE_NUMBER ?? '+918657048982',
sessionId: process.env.WHATSAPP_SESSION_ID ?? 'production-sender',
botPhoneNumber: process.env.BOT_PHONE_NUMBER ?? '',
sessionId: process.env.WHATSAPP_SESSION_ID ?? 'production-sender',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wwebjs/smoke/flows.ts` around lines 44 - 45, The botPhoneNumber fallback
currently hard-codes a real number; remove the '+918657048982' default and make
BOT_PHONE_NUMBER required by replacing the inline fallback with a strict check
(read process.env.BOT_PHONE_NUMBER into botPhoneNumber and if falsy throw a
clear error during startup), or set botPhoneNumber to undefined and add startup
validation that throws; update any code that uses botPhoneNumber (e.g.,
references in flows.ts) to rely on this validation so the process fails fast
instead of sending to an unintended recipient.

gcsBucket: process.env.GCS_BUCKET ?? '',
instatus: {
pageId: process.env.INSTATUS_PAGE_ID ?? '',
componentId: process.env.INSTATUS_COMPONENT_ID ?? '',
},
},
staging: {
name: 'staging',
botPhoneNumber: process.env.BOT_PHONE_NUMBER ?? '',
sessionId: process.env.WHATSAPP_SESSION_ID ?? 'staging-sender',
gcsBucket: process.env.GCS_BUCKET ?? '',
instatus: {
pageId: process.env.INSTATUS_PAGE_ID ?? '',
componentId: process.env.INSTATUS_COMPONENT_ID ?? '',
},
},
};
78 changes: 78 additions & 0 deletions wwebjs/smoke/gcs-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Storage } from '@google-cloud/storage';
import * as fs from 'fs';
import * as path from 'path';

export interface GCSStoreOptions {
bucket: string;
keyFilename?: string;
prefix?: string;
/** Must match RemoteAuth dataPath (default: ./.wwebjs_auth/) */
dataPath?: string;
}

export interface StoreSessionParams {
session: string;
path?: string;
}

export class GCSRemoteAuthStore {
private storage: Storage;
private bucket: string;
private prefix: string;
private dataPath: string;

constructor(options: GCSStoreOptions) {
this.storage = new Storage(options.keyFilename ? { keyFilename: options.keyFilename } : {});
this.bucket = options.bucket;
this.prefix = options.prefix ?? 'whatsapp-sessions';
this.dataPath = path.resolve(options.dataPath ?? './.wwebjs_auth');
}

/** RemoteAuth writes zips as {dataPath}/{session}.zip (e.g. RemoteAuth-production-sender.zip). */
private localZipPath(session: string): string {
return path.join(this.dataPath, `${session}.zip`);
}

private objectName(session: string): string {
return `${this.prefix}/${session}.zip`;
}

async sessionExists({ session }: { session: string }): Promise<boolean> {
console.log(`Checking if session exists: ${session}`);
const [exists] = await this.storage.bucket(this.bucket).file(this.objectName(session)).exists();
return exists;
}

async save({ session }: StoreSessionParams): Promise<void> {
console.log(`Saving session: ${session}`);
const localZip = this.localZipPath(session);
if (!fs.existsSync(localZip)) {
throw new Error(`Session zip not found at ${localZip}`);
}
await this.storage
.bucket(this.bucket)
.upload(localZip, { destination: this.objectName(session) });
console.log(`Session saved to GCS: gs://${this.bucket}/${this.objectName(session)}`);
}

async extract({ session, path: destPath }: StoreSessionParams): Promise<void> {
console.log(`Extracting session: ${session}`);
const localZip = destPath ?? this.localZipPath(session);
fs.mkdirSync(path.dirname(localZip), { recursive: true });
await this.storage
.bucket(this.bucket)
.file(this.objectName(session))
.download({ destination: localZip });
console.log(`Session restored from GCS: gs://${this.bucket}/${this.objectName(session)}`);
}

async delete({ session }: { session: string }): Promise<void> {
console.log(`Deleting session: ${session}`);
try {
await this.storage.bucket(this.bucket).file(this.objectName(session)).delete();
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException)?.code !== '404') throw err;
}
Comment on lines +73 to +75
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In the Node.js @google-cloud/storageclient, what fields/types are used on errors thrown byfile.delete() when the object does not exist?

💡 Result:

When you call bucket.file(name).delete() and the object does not exist, the client throws an ApiError (from @google-cloud/common) whose error body includes these fields, which are then exposed as properties on the thrown error: 1) code (number | undefined) - ApiError has a code?: number property. [1] - The thrown error for a missing object includes code: 404 in its body. [2] 2) message (string) - ApiError is constructed with a message string (and thus the thrown error has e.message). [3] - The missing-object case shows a message like No such object: <bucket>/<object>. [4] 3) errors (GoogleInnerError[] | undefined) - ApiError has an errors?: GoogleInnerError[] property. [1] - In the missing-object case, the error body includes errors: [Array]. [2] 4) response (r.Response | undefined) - ApiError has a response?: r.Response property. [1] - This response is set from errorBody.response in @google-cloud/common’s util. [3] Additionally, for the specific “not found” HTTP status: - The HTTP status code is 404, as shown by the missing-object error body having code: 404. [2] These are the fields/types you can rely on on the thrown error instance for this scenario: code?: number, message: string, errors?: GoogleInnerError[], and response?: r.Response. [1][3][2][4]

Citations:


Handle GCS “not found” errors by checking numeric HTTP 404.

bucket.file(...).delete() throws an ApiError where the missing-object case sets code: 404 as a number; comparing to '404' can incorrectly rethrow. Update the catch to test err.code !== 404 (optionally fall back to err.response?.statusCode), and don’t rely on statusCode being a top-level field.

Suggested fix
-    } catch (err: unknown) {
-      if ((err as NodeJS.ErrnoException)?.code !== '404') throw err;
+    } catch (err: unknown) {
+      const e = err as { code?: number; response?: { statusCode?: number } };
+      if (e.code !== 404 && e.response?.statusCode !== 404) throw err;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException)?.code !== '404') throw err;
}
} catch (err: unknown) {
const e = err as { code?: number; response?: { statusCode?: number } };
if (e.code !== 404 && e.response?.statusCode !== 404) throw err;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wwebjs/smoke/gcs-store.ts` around lines 73 - 75, The catch block that handles
deletion from GCS currently checks (err as NodeJS.ErrnoException)?.code !==
'404' using a string; change this to treat the GCS ApiError code as a number
(compare to 404) and additionally tolerate the HTTP response shape by checking
err.code !== 404 || err.response?.statusCode !== 404 (or explicitly accept
either 404 as number or response.statusCode === 404) so that missing-object
errors are swallowed and other errors are rethrown; update the catch around the
bucket.file(...).delete() call accordingly and avoid relying on a top-level
string '404'.

console.log(`Session deleted from GCS: ${this.objectName(session)}`);
}
}
26 changes: 26 additions & 0 deletions wwebjs/smoke/instatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export async function reportToInstatus(
apiKey: string,
pageId: string,
componentId: string,
passed: boolean
): Promise<void> {
if (!apiKey || !pageId || !componentId) {
console.warn('Instatus credentials not set — skipping status report');
return;
}
const status = passed ? 'OPERATIONAL' : 'MAJOROUTAGE';
const url = `https://api.instatus.com/v2/${pageId}/components/${componentId}`;
try {
const res = await fetch(url, {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status }),
});
console.log(`Instatus report: ${status} → HTTP ${res.status}`);
} catch (err) {
Comment on lines +14 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Instatus v2 component update endpoints, are non-2xx responses considered failed updates that clients should explicitly handle?

💡 Result:

Yes. For Instatus v2 component update endpoints (PUT /v2/:page_id/components/:component_id), you should treat any non-2xx HTTP response as a failed update that clients must handle explicitly. Instatus’s API documentation states that “Responses from the Instatus API, including errors, are encoded as JSON” and that error responses include a code and message in the payload (so clients should not assume that only the request transport succeeded) [1]. For component updates, the API reference documents the update endpoint and shows a normal 200-style success example response when the update succeeds [2][3]. Given that Instatus uses explicit error payloads with an “error” object (code/message) and that non-2xx status codes are categorized by HTTP as client/server errors rather than successful responses [4], a non-2xx response indicates the server did not complete the update. Practical client guidance: consider the update successful only when the HTTP status is in the 2xx range and you can parse the success response body; otherwise, read the JSON error payload (error.code / error.message) and handle/retry/alert as appropriate [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the referenced file around the stated lines
ls -la
sed -n '1,120p' wwebjs/smoke/instatus.ts

# Check for any other fetch/error handling patterns in the repo
rg -n "instatus.*fetch|fetch\\(url|res\\.ok|HTTP .*res\\.status|reported|Instatus report" wwebjs/smoke/instatus.ts wwebjs -S

Repository: glific/cypress-testing

Length of output: 2434


Treat non-2xx Instatus responses as failures.

reportToInstatus logs success for any HTTP status and only treats network/transport errors as failures (the catch won’t run for 401/404/5xx). Check res.ok and fail the report on non-2xx.

Suggested fix
     const res = await fetch(url, {
       method: 'PUT',
       headers: {
         Authorization: `Bearer ${apiKey}`,
         'Content-Type': 'application/json',
       },
       body: JSON.stringify({ status }),
     });
-    console.log(`Instatus report: ${status} → HTTP ${res.status}`);
+    if (!res.ok) {
+      throw new Error(`Instatus report failed with HTTP ${res.status}`);
+    }
+    console.log(`Instatus report: ${status} → HTTP ${res.status}`);
For Instatus v2 component update endpoints, are non-2xx responses considered failed updates that clients should explicitly handle?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wwebjs/smoke/instatus.ts` around lines 14 - 23, The function reportToInstatus
currently logs success for any HTTP response and only treats transport errors as
failures; update reportToInstatus to check the fetch Response (res) using res.ok
after the PUT and treat non-2xx responses as failures by logging an error and
throwing or returning a rejected result so callers can handle it; locate the
fetch call and the console.log("Instatus report...") that follows it, replace or
augment that logic to inspect res.ok (and optionally include
res.status/res.statusText and body) and handle non-ok responses as errors.

console.error('Instatus report failed:', err);
}
}
Loading
Loading