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
5 changes: 5 additions & 0 deletions apps/api/src/app/integrations/dtos/credentials.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,9 @@ export class CredentialsDto implements ICredentials {
@IsOptional()
@IsString()
AppIOBaseUrl?: string;

@ApiPropertyOptional()
@IsString()
@IsOptional()
accessSecret?: string;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -297,15 +297,23 @@ export class SendMessageChat extends SendMessageBase {
Object.values(ChatProviderIdEnum).includes(chan.providerId as ChatProviderIdEnum)
) || [];

// Add WhatsApp Business if subscriber has phone
// Add WhatsApp APIs if subscriber has phone
if (subscriber.phone) {
// @ts-expect-error - Adding WhatsApp channel without _integrationId
// @ts-expect-error - Adding WhatsApp Business channel without _integrationId
chatChannels.push({
providerId: ChatProviderIdEnum.WhatsAppBusiness,
credentials: {
phoneNumber: subscriber.phone,
},
});

// @ts-expect-error - Adding Maqsam WhatsApp channel without _integrationId
chatChannels.push({
providerId: ChatProviderIdEnum.MaqsamWhatsApp,
credentials: {
phoneNumber: subscriber.phone,
},
})
}

return chatChannels;
Expand Down
2 changes: 2 additions & 0 deletions libs/application-generic/src/factories/chat/chat.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChatWebhookHandler } from './handlers/chat-webhook.handler';
import { DiscordHandler } from './handlers/discord.handler';
import { GetstreamChatHandler } from './handlers/getstream.handler';
import { GrafanaOnCallHandler } from './handlers/grafana-on-call.handler';
import { MaqsamWhatsAppHandler } from './handlers/maqsam.handler';
import { MattermostHandler } from './handlers/mattermost.handler';
import { MSTeamsHandler } from './handlers/msteams.handler';
import { NovuSlackHandler } from './handlers/novu-slack.handler';
Expand All @@ -27,6 +28,7 @@ export class ChatFactory implements IChatFactory {
new GetstreamChatHandler(),
new RocketChatHandler(),
new WhatsAppBusinessHandler(),
new MaqsamWhatsAppHandler(),
];

getHandler(integration: Pick<IntegrationEntity, 'credentials' | 'channel' | 'providerId' | 'configurations'>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MaqsamWhatsAppProvider } from '@novu/providers';
import { ChannelTypeEnum, ChatProviderIdEnum, ICredentials } from '@novu/shared';
import { BaseChatHandler } from './base.handler';

export class MaqsamWhatsAppHandler extends BaseChatHandler {
constructor() {
super(ChatProviderIdEnum.MaqsamWhatsApp, ChannelTypeEnum.CHAT);
}

buildProvider(credentials: ICredentials) {
const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };

this.provider = new MaqsamWhatsAppProvider(config);
}
Comment on lines +10 to +14
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add credential validation to prevent runtime errors.

The buildProvider method doesn't validate that credentials.accessKey and credentials.accessSecret are defined before passing them to the provider. If either credential is missing, the provider would create an invalid Basic auth token like "dW5kZWZpbmVkOnVuZGVmaW5lZA==" (base64 of "undefined:undefined"), causing API authentication failures.

🔎 Recommended validation
 buildProvider(credentials: ICredentials) {
+  if (!credentials.accessKey || !credentials.accessSecret) {
+    throw new Error('Maqsam WhatsApp provider requires both accessKey and accessSecret');
+  }
+
   const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };
 
   this.provider = new MaqsamWhatsAppProvider(config);
 }

Alternatively, use non-null assertions if validation happens upstream:

-  const config: { accessKey: string, accessSecret: string } = { accessKey: credentials.accessKey, accessSecret: credentials.accessSecret };
+  const config = { 
+    accessKey: credentials.accessKey!, 
+    accessSecret: credentials.accessSecret! 
+  };
🤖 Prompt for AI Agents
In libs/application-generic/src/factories/chat/handlers/maqsam.handler.ts around
lines 10 to 14, the buildProvider method currently constructs a config using
credentials.accessKey and credentials.accessSecret without validating them; add
explicit checks that both accessKey and accessSecret are non-empty strings
(e.g., typeof === 'string' && length > 0) and throw a clear Error or return
early with a logged error if validation fails, so you never pass undefined to
MaqsamWhatsAppProvider; after validation, construct the config and instantiate
the provider as before.

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const integrationSchema = new Schema<IntegrationDBModel>(
AppIOSubscriptionId: Schema.Types.String,
AppIOBearerToken: Schema.Types.String,
AppIOOriginalSignature: Schema.Types.String,
accessSecret: Schema.Types.String,
},
configurations: {
inboundWebhookEnabled: Schema.Types.Boolean,
Expand Down
1 change: 1 addition & 0 deletions libs/internal-sdk/src/models/components/providersidenum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const ProvidersIdEnum = {
WhatsappBusiness: "whatsapp-business",
ChatWebhook: "chat-webhook",
NovuSlack: "novu-slack",
MaqsamWhatsApp: 'maqsam-whatsapp',
} as const;
/**
* Provider ID of the job
Expand Down
1 change: 1 addition & 0 deletions packages/framework/src/schemas/providers/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const chatProviderSchemas = {
slack: slackProviderSchemas,
'whatsapp-business': genericProviderSchemas,
zulip: genericProviderSchemas,
'maqsam-whatsapp': genericProviderSchemas,
} as const satisfies Record<ChatProviderIdEnum, { output: JsonSchema }>;
1 change: 1 addition & 0 deletions packages/framework/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export enum ChatProviderIdEnum {
RocketChat = 'rocket-chat',
WhatsAppBusiness = 'whatsapp-business',
ChatWebhook = 'chat-webhook',
MaqsamWhatsApp = 'maqsam-whatsapp',
}

export enum PushProviderIdEnum {
Expand Down
1 change: 1 addition & 0 deletions packages/providers/src/lib/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './chat-webhook/chat-webhook.provider';
export * from './discord/discord.provider';
export * from './getstream/getstream.provider';
export * from './grafana-on-call/grafana-on-call.provider';
export * from './maqsam-whatsapp/maqsam-whatsapp.provider';
export * from './mattermost/mattermost.provider';
export * from './msTeams/msTeams.provider';
export * from './rocket-chat/rocket-chat.provider';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { IChatOptions } from '@novu/stateless';
import { nanoid } from 'nanoid';
import { expect, test } from 'vitest';
import { axiosSpy } from '../../../utils/test/spy-axios';
import { MaqsamWhatsAppProvider } from './maqsam-whatsapp.provider';

const mockProviderConfig = {
accessKey: 'my-access-key',
accessSecret: 'my-access-secret',
};

const buildResponse = (messageId: string) => {
return {
data: {
message_id: messageId,
message_status: 'submitted',
},
};
};

test('should trigger maqsam library correctly with template message', async () => {
const messageId = nanoid();

const { mockPost, axiosMockSpy } = axiosSpy(buildResponse(messageId));

const provider = new MaqsamWhatsAppProvider(mockProviderConfig);

const options: IChatOptions = {
phoneNumber: '+111111111',
content: 'Template message',
customData: {
templateId: '123',
templateVariables: {
name: 'John',
company: 'Acme',
},
},
};
Comment on lines +28 to +38
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Test uses deprecated phoneNumber field but provider expects channelData.

Lines 29 and 44 use the deprecated phoneNumber field, but according to the provider implementation (from the relevant code snippet), line 35 extracts the phone number from (options.channelData as PhoneData).endpoint.phoneNumber. This mismatch means the tests don't accurately validate the provider's actual behavior and would likely fail at runtime if the provider is called with these test options.

🔎 Corrected test structure
Also applies to: 40-47

<details>
<summary>🤖 Prompt for AI Agents</summary>

In
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts
around lines 28 to 38 (and also apply same change to lines ~40-47), the test
builds options using the deprecated phoneNumber field but the provider reads the
recipient from options.channelData.endpoint.phoneNumber; update the test options
to include channelData: { endpoint: { phoneNumber: '' } } (move the
existing '+111111111' into that structure) and remove or stop using the
top-level phoneNumber field so the spec mirrors the provider’s expected input
shape and validates actual behavior.


</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->


const res = await provider.sendMessage(options);

expect(mockPost).toHaveBeenCalled();
expect(mockPost).toHaveBeenCalledWith('https://api.maqsam.com/v2/whatsapp/messages/send_message', {
RecipientPhone: options.phoneNumber,
TemplateId: options.customData?.templateId,
TemplateVariables: options.customData?.templateVariables,
});

const token = Buffer.from(`${mockProviderConfig.accessKey}:${mockProviderConfig.accessSecret}`).toString('base64');

expect(axiosMockSpy).toHaveBeenCalledWith({
headers: {
Authorization: `Basic ${token}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
});

expect(res.id).toBe(messageId);
});

test('should trigger maqsam library correctly with template message with _passthrough', async () => {
const messageId = nanoid();

const { mockPost, axiosMockSpy } = axiosSpy(buildResponse(messageId));

const provider = new MaqsamWhatsAppProvider(mockProviderConfig);

const options: IChatOptions = {
phoneNumber: '+111111111',
content: 'Template message',
customData: {
templateId: '123',
templateVariables: {
name: 'John',
company: 'Acme',
},
},
};

const res = await provider.sendMessage(options, {
_passthrough: {
query: {
RecipientPhone: '+111111111',
TemplateId: '123',
TemplateVariables: JSON.stringify({
name: 'John',
company: 'Acme',
}),
},
},
});

expect(mockPost).toHaveBeenCalled();
expect(mockPost).toHaveBeenCalledWith('https://api.maqsam.com/v2/whatsapp/messages/send_message', {
RecipientPhone: '+111111111',
TemplateId: '123',
TemplateVariables: {
name: 'John',
company: 'Acme',
},
});

const token = Buffer.from(`${mockProviderConfig.accessKey}:${mockProviderConfig.accessSecret}`).toString('base64');

expect(axiosMockSpy).toHaveBeenCalledWith({
headers: {
Authorization: `Basic ${token}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
});

expect(res.id).toBe(messageId);
});

test('should throw error when message status is not submitted', async () => {
const messageId = nanoid();

axiosSpy({
data: {
message_id: messageId,
message_status: 'failed',
},
});

const provider = new MaqsamWhatsAppProvider(mockProviderConfig);

const options: IChatOptions = {
phoneNumber: '+111111111',
content: 'Template message',
customData: {
templateId: '123',
templateVariables: {
name: 'John',
company: 'Acme',
},
},
};

await expect(provider.sendMessage(options)).rejects.toThrow('Maqsam Chat failed:');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
ChannelTypeEnum,
IChatOptions,
IChatProvider,
ISendMessageSuccessResponse,
PhoneData,
} from "@novu/stateless";
import Axios, { AxiosInstance } from 'axios';
import { BaseProvider, CasingEnum } from "../../../base.provider";
import { WithPassthrough } from "../../../utils/types";
import { ISendMessageFailureRes, ISendMessageRes } from "./types/maqsam-whatsapp.types";

export class MaqsamWhatsAppProvider extends BaseProvider implements IChatProvider {
id = "maqsam-whatsapp";
channelType = ChannelTypeEnum.CHAT as ChannelTypeEnum.CHAT;
protected casing: CasingEnum = CasingEnum.CAMEL_CASE;

private readonly axiosClient: AxiosInstance;
private readonly baseUrl = 'https://api.maqsam.com/v2/whatsapp/messages/send_message';

constructor(private config: {
accessKey: string;
accessSecret: string;
}) {
super();

const token = Buffer.from(`${this.config.accessKey}:${this.config.accessSecret}`).toString('base64');

this.axiosClient = Axios.create({
headers: {
Authorization: `Basic ${token}`,
'Content-Type': 'application/x-www-form-urlencoded',
Comment on lines +30 to +32
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the maqsam-whatsapp provider file
cat -n packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 2572


🏁 Script executed:

# Find the BaseProvider class and its transform method
fd -t f "base.provider" | head -20

Repository: novuhq/novu

Length of output: 148


🏁 Script executed:

# Examine the BaseProvider file to find the transform method
cat -n packages/providers/src/base.provider.ts

Repository: novuhq/novu

Length of output: 5436


🏁 Script executed:

# Search for URL encoding logic and axios transformers in the maqsam whatsapp provider
rg -n "URLSearchParams|urlencoded|qs\.|stringify" packages/providers/src/lib/chat/maqsam-whatsapp/ -A 3 -B 3

# Also check if there are test files
find packages/providers -path "*maqsam*" -type f | grep -i test

Repository: novuhq/novu

Length of output: 3691


🏁 Script executed:

# Examine the test file to understand the expected behavior
cat -n packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 4728


🏁 Script executed:

# Find the axiosSpy utility
find packages/providers -path "*test*" -name "*spy*axios*" -type f

Repository: novuhq/novu

Length of output: 101


🏁 Script executed:

# Examine the axiosSpy utility
cat -n packages/providers/src/utils/test/spy-axios.ts

Repository: novuhq/novu

Length of output: 1380


🏁 Script executed:

# Verify if axios has any custom transformers or if there's URL encoding elsewhere
# Check the axios client creation in maqsam-whatsapp.provider.ts more carefully
grep -A 10 "Axios.create" packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

# Check if URLSearchParams or qs is installed
find packages/providers -name "package.json" | xargs grep -l "qs\|query-string"

Repository: novuhq/novu

Length of output: 433


🏁 Script executed:

# Check dependencies in providers package.json
cat packages/providers/package.json | grep -A 50 "dependencies"

Repository: novuhq/novu

Length of output: 1589


🏁 Script executed:

# Verify that qs is not imported or used in maqsam-whatsapp files
rg -n "import.*qs|from.*qs" packages/providers/src/lib/chat/maqsam-whatsapp/

Repository: novuhq/novu

Length of output: 374


🏁 Script executed:

# Final verification: Check actual axios default behavior documentation 
# by looking at how other providers in the codebase handle form-urlencoded
rg -n "application/x-www-form-urlencoded" packages/providers/src/lib/ -B 5 -A 5

Repository: novuhq/novu

Length of output: 7106


Serialize payload to form-urlencoded format before sending.

The Content-Type header is set to 'application/x-www-form-urlencoded' (line 32), but the code posts payload.body directly as a JavaScript object (line 52). Axios will serialize the object as JSON by default, violating the declared Content-Type and causing the API request to fail.

Use qs.stringify(payload.body) or URLSearchParams to serialize the payload into the key=value&key=value format required by this Content-Type. Other providers in the codebase (unifonic, isendpro-sms) follow this pattern correctly.

🤖 Prompt for AI Agents
In packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
around lines 30 to 32, the request sets Content-Type:
'application/x-www-form-urlencoded' but sends payload.body as a plain JS object;
update the sending logic to serialize the body into form-urlencoded format
(e.g., use qs.stringify(payload.body) or new
URLSearchParams(payload.body).toString()) before passing it to axios so the
payload matches the header; add/import qs if using it and ensure axios receives
the serialized string while keeping the existing headers.

},
});
}

private defineMessagePayload(options: IChatOptions) {
return {
RecipientPhone: (options.channelData as PhoneData).endpoint.phoneNumber,
TemplateId: options.customData?.templateId,
TemplateVariables: options.customData?.templateVariables,
}
}
Comment on lines +37 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation for channelData and required customData properties.

Line 39 performs an unsafe type cast without validating that channelData exists or is actually of type PhoneData. If channelData is undefined or of a different type, this will cause a runtime error.

Lines 40-41 use optional chaining for customData, but don't validate that templateId is present. Since these appear to be required by the Maqsam API (based on the API structure), sending undefined values will likely cause API failures.

🔎 Proposed fix with validation
 private defineMessagePayload(options: IChatOptions) {
+  if (!options.channelData || options.channelData.type !== 'phone') {
+    throw new Error('Phone number is required in channelData for Maqsam WhatsApp');
+  }
+  
+  if (!options.customData?.templateId) {
+    throw new Error('templateId is required in customData for Maqsam WhatsApp');
+  }
+
+  const phoneData = options.channelData as PhoneData;
+
   return {
-    RecipientPhone: (options.channelData as PhoneData).endpoint.phoneNumber,
+    RecipientPhone: phoneData.endpoint.phoneNumber,
     TemplateId: options.customData?.templateId,
     TemplateVariables: options.customData?.templateVariables,
   }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
around lines 37 to 43, the method defineMessagePayload unsafely casts
channelData to PhoneData and allows missing customData fields; add runtime
validation: check that options.channelData exists and has an
endpoint.phoneNumber (use a small type-guard or explicit checks) and throw or
return a descriptive error if missing; verify options.customData and that
customData.templateId (and templateVariables if required) are present and
throw/return a clear error when absent; after validation, construct and return
the payload using the validated values (avoid direct casting and optional
chaining for required API fields).


async sendMessage(
options: IChatOptions,
bridgeProviderData: WithPassthrough<Record<string, unknown>> = {},
): Promise<ISendMessageSuccessResponse> {
const payload = this.transform(bridgeProviderData, this.defineMessagePayload(options));

const { data } = await this.axiosClient.post<ISendMessageRes | ISendMessageFailureRes>(this.baseUrl,
payload.body,
);

if ('conversationId' in data && data.conversationId) {
return {
id: data.conversationId,
date: new Date().toISOString(),
};
}

throw new Error(`Maqsam Chat failed: ${JSON.stringify(data || {})}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ISendMessageRes {
conversationId: string;
messageStatus: string;
result: string;
}
Comment on lines +1 to +5
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -path "*maqsam*whatsapp*" -name "*.ts" -o -path "*maqsam*whatsapp*" -name "*.json" | head -20

Repository: novuhq/novu

Length of output: 296


🏁 Script executed:

# Get the full path to the types file mentioned in the review
find . -type f -path "*maqsam-whatsapp.types.ts"

Repository: novuhq/novu

Length of output: 135


🏁 Script executed:

# Search for provider file
find . -type f -path "*maqsam-whatsapp.provider.ts"

Repository: novuhq/novu

Length of output: 132


🏁 Script executed:

# Search for test files related to maqsam
find . -type f -path "*maqsam*" -name "*.spec.ts" -o -path "*maqsam*" -name "*.test.ts"

Repository: novuhq/novu

Length of output: 201


🏁 Script executed:

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts

Repository: novuhq/novu

Length of output: 309


🏁 Script executed:

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 2572


🏁 Script executed:

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 4728


🏁 Script executed:

find . -type f -name "base.provider.ts" | head -5

Repository: novuhq/novu

Length of output: 96


🏁 Script executed:

# Also search for how CasingEnum.CAMEL_CASE is used
rg "CasingEnum" -A 5 -B 5 --type ts | head -100

Repository: novuhq/novu

Length of output: 9418


🏁 Script executed:

cat -n ./packages/providers/src/base.provider.ts | head -150

Repository: novuhq/novu

Length of output: 5436


🏁 Script executed:

# Search for the transform method in base provider
rg "transform\(" -A 20 ./packages/providers/src/base.provider.ts | head -80

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Check if there's any transformation happening in axiosSpy or if tests are mocking correctly
rg "axiosSpy" -B 10 -A 10 ./packages/providers/src/utils/test/spy-axios.ts 2>/dev/null | head -100

Repository: novuhq/novu

Length of output: 595


🏁 Script executed:

# Verify the actual flow - does the response data get transformed anywhere?
rg "casingTransform\|transform(" ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 159


🏁 Script executed:

# Check if there's any middleware or interceptor that might transform response data
rg "interceptor\|response" ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Check what the response flow is - is data transformed after axios call?
rg "casingTransform" ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Look at how axios response is handled - does it go through any transformation?
rg "axiosClient.post" -A 5 ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 262


🏁 Script executed:

# Let me check the actual transformation flow more carefully
# Look at line 49 - where transform is called
sed -n '45,65p' ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts

Repository: novuhq/novu

Length of output: 687


🏁 Script executed:

# Let me verify my understanding - check if axiosSpy does any transformation or just returns data as-is
cat -n ./packages/providers/src/utils/test/spy-axios.ts

Repository: novuhq/novu

Length of output: 1380


🏁 Script executed:

# Verify the critical path - does the test actually pass?
# Let me check if there are any recent test results or CI logs
# First, let me see what the test framework is and if we can understand test behavior

# Let's trace through the logic one more time:
# 1. Mock returns: { message_id, message_status }
# 2. Provider checks: 'conversationId' in data
# 3. This check should fail

# Let me verify by looking at another provider to see how they handle responses
find . -type f -path "*provider.spec.ts" -type f | head -3 | xargs -I {} bash -c 'echo "=== {} ===" && cat -n {} | head -80'

Repository: novuhq/novu

Length of output: 7371


🏁 Script executed:

# Let me check if maybe the transform is being applied to responses too
# by searching for any response transformation in base provider
rg "response\|data" ./packages/providers/src/base.provider.ts | grep -i "transform\|case"

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Critical check - does the response transformation happen somewhere I missed?
# Let me trace the exact flow: what does casingTransform do to the RESPONSE?

# First, let me check if there are any response interceptors in axios config
grep -r "interceptor" ./packages/providers/src/lib/chat/maqsam-whatsapp/

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Let me also verify if there's any middleware transforming responses
# by searching for any response transformation in similar providers
rg "const { data }" ./packages/providers/src/lib/chat/ -A 3 | head -60

Repository: novuhq/novu

Length of output: 159


🏁 Script executed:

# Most importantly - let me trace the actual test execution path
# The test mocks the response to have message_id, but provider checks for conversationId
# This should fail, but the test expects it to pass. Let me verify this is indeed the case:

sed -n '12,19p' ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 203


🏁 Script executed:

# Show the test assertion that expects this to succeed
sed -n '58,58p' ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.spec.ts

Repository: novuhq/novu

Length of output: 88


🏁 Script executed:

# Let me verify one more critical thing - is the test actually passing or failing?
# Let me check if there are any CI test results or if I can understand what should happen

# The key question: when mock returns { message_id }, does provider.sendMessage() succeed?
# Based on the code, it should FAIL because:
# 1. Mock: { message_id: 'xyz' }
# 2. Provider checks: 'conversationId' in data → FALSE
# 3. Provider throws error

# So test line 58 (expect(res.id).toBe(messageId)) should FAIL

# Let me verify by checking if there's any response transformation I missed
# by looking at the complete flow one more time in the provider

cat -n ./packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts | sed -n '45,63p'

Repository: novuhq/novu

Length of output: 818


🏁 Script executed:

# Also check - does BaseProvider have any response transformer?
grep -n "response\|Response" ./packages/providers/src/base.provider.ts

Repository: novuhq/novu

Length of output: 37


🏁 Script executed:

# Final verification - check if axios.post response is transformed somewhere
# by searching for any response interceptors or transformers in the entire provider
rg "transformResponse\|responseTransformer" ./packages/providers/src/

Repository: novuhq/novu

Length of output: 37


Critical: Response field mismatch causes provider failure.

The ISendMessageRes interface defines conversationId, but the test mocks show the Maqsam API returns message_id in snake_case. The provider's success check (if ('conversationId' in data && data.conversationId)) will always fail, throwing an error for all API responses.

The interface must be updated to match the actual API response:

 export interface ISendMessageRes {
-  conversationId: string;
+  messageId: string;
   messageStatus: string;
   result: string;
 }

And the provider check accordingly (in maqsam-whatsapp.provider.ts, line 55):

-if ('conversationId' in data && data.conversationId) {
+if ('messageId' in data && data.messageId) {
   return {
-    id: data.conversationId,
+    id: data.messageId,
     date: new Date().toISOString(),
   };
 }
📝 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
export interface ISendMessageRes {
conversationId: string;
messageStatus: string;
result: string;
}
export interface ISendMessageRes {
messageId: string;
messageStatus: string;
result: string;
}
🤖 Prompt for AI Agents
In
packages/providers/src/lib/chat/maqsam-whatsapp/types/maqsam-whatsapp.types.ts
lines 1-5, the interface fields do not match the real Maqsam API response (tests
show snake_case). Replace conversationId with message_id (string) and ensure
other fields reflect the actual response names; then update the provider success
check in
packages/providers/src/lib/chat/maqsam-whatsapp/maqsam-whatsapp.provider.ts
(around line 55) to test for 'message_id' in the response and use
data.message_id instead of data.conversationId so the provider recognizes
successful API responses.


export interface ISendMessageFailureRes {
error: string
message: string
}
10 changes: 10 additions & 0 deletions packages/shared/src/consts/providers/channels/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
chatWebhookConfig,
getstreamConfig,
grafanaOnCallConfig,
maqsamWhatsAppChatConfig,
msTeamsConfig,
rocketChatConfig,
slackConfigLegacy,
Expand Down Expand Up @@ -109,4 +110,13 @@ export const chatProviders: IProviderConfig[] = [
logoFileName: { light: 'chat-webhook.svg', dark: 'chat-webhook.svg' },
betaVersion: true,
},
{
id: ChatProviderIdEnum.MaqsamWhatsApp,
displayName: 'Maqsam WhatsApp',
channel: ChannelTypeEnum.CHAT,
credentials: maqsamWhatsAppChatConfig,
docReference: 'https://portal.maqsam.com/docs/v2/whatsapp',
logoFileName: { light: 'maqsam-whatsapp.svg', dark: 'maqsam-whatsapp.svg' },
betaVersion: true,
}
];
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,21 @@ export const maqsamConfig: IConfigCredential[] = [
...smsConfigBase,
];

export const maqsamWhatsAppChatConfig: IConfigCredential[] = [
{
key: CredentialsKeyEnum.AccessKey,
displayName: 'Access Key',
type: 'string',
required: true,
},
{
key: CredentialsKeyEnum.AccessSecret,
displayName: 'Access Secret',
type: 'string',
required: true,
}
];

export const smsCentralConfig: IConfigCredential[] = [
{
key: CredentialsKeyEnum.User,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const secureCredentials: CredentialsKeyEnum[] = [
CredentialsKeyEnum.Token,
CredentialsKeyEnum.Password,
CredentialsKeyEnum.ServiceAccount,
CredentialsKeyEnum.AccessSecret
];
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ export interface ICredentials {
AppIOOriginalSignature?: string;
servicePlanId?: string;
tenantId?: string;
accessSecret?: string;
}
Loading
Loading