first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import { MicrosoftTeamsTrigger } from '../../MicrosoftTeamsTrigger.node';
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../v2/transport';
|
||||
|
||||
jest.mock('../../v2/transport', () => ({
|
||||
microsoftApiRequest: {
|
||||
call: jest.fn(),
|
||||
},
|
||||
microsoftApiRequestAllItems: {
|
||||
call: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Microsoft Teams Trigger Node', () => {
|
||||
let mockWebhookFunctions: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockWebhookFunctions = mock();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('webhookMethods', () => {
|
||||
describe('checkExists', () => {
|
||||
it('should return true if the subscription exists', async () => {
|
||||
(microsoftApiRequestAllItems.call as jest.Mock).mockResolvedValue([
|
||||
{
|
||||
id: 'sub1',
|
||||
notificationUrl: 'https://webhook.url',
|
||||
resource: '/me/chats',
|
||||
expirationDateTime: new Date(Date.now() + 3600000).toISOString(),
|
||||
},
|
||||
]);
|
||||
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('https://webhook.url');
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
node: {
|
||||
subscriptionIds: ['sub1'],
|
||||
},
|
||||
});
|
||||
|
||||
mockWebhookFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
if (paramName === 'event') return 'newChat';
|
||||
return false;
|
||||
});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.checkExists.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
it('should return false if the subscription does not exist', async () => {
|
||||
(microsoftApiRequestAllItems.call as jest.Mock).mockResolvedValue([]);
|
||||
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('https://webhook.url');
|
||||
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
node: {},
|
||||
});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.checkExists.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should throw an error if the API request fails', async () => {
|
||||
(microsoftApiRequestAllItems.call as jest.Mock).mockRejectedValue(
|
||||
new Error('API request failed'),
|
||||
);
|
||||
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('https://webhook.url');
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
node: {},
|
||||
});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.checkExists.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a subscription successfully', async () => {
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValue({ id: 'subscription123' });
|
||||
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('https://webhook.url');
|
||||
mockWebhookFunctions.getNodeParameter.mockReturnValue('newChat');
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
node: {
|
||||
subscriptionIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValue({
|
||||
value: [{ id: 'team1', displayName: 'Team 1' }],
|
||||
});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.create.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(microsoftApiRequest.call).toHaveBeenCalledWith(
|
||||
mockWebhookFunctions,
|
||||
'POST',
|
||||
'/v1.0/subscriptions',
|
||||
expect.objectContaining({
|
||||
changeType: 'created',
|
||||
notificationUrl: 'https://webhook.url',
|
||||
resource: '/me/chats',
|
||||
expirationDateTime: expect.any(String),
|
||||
latestSupportedTlsVersion: 'v1_2',
|
||||
lifecycleNotificationUrl: 'https://webhook.url',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if the URL is invalid', async () => {
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('invalid-url');
|
||||
await expect(
|
||||
new MicrosoftTeamsTrigger().webhookMethods.default.create.call(mockWebhookFunctions),
|
||||
).rejects.toThrow('Invalid Notification URL');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete subscriptions using stored IDs and clean static data', async () => {
|
||||
const mockWebhookData = {
|
||||
subscriptionIds: ['subscription123'],
|
||||
};
|
||||
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue(mockWebhookData);
|
||||
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValue({});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.delete.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
expect(microsoftApiRequest.call).toHaveBeenCalledWith(
|
||||
mockWebhookFunctions,
|
||||
'DELETE',
|
||||
'/v1.0/subscriptions/subscription123',
|
||||
);
|
||||
expect(mockWebhookData.subscriptionIds).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return false if no subscription matches', async () => {
|
||||
(microsoftApiRequestAllItems.call as jest.Mock).mockResolvedValue([]);
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('https://webhook.url');
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
node: {
|
||||
subscriptionIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.delete.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should throw an error if the API request fails', async () => {
|
||||
(microsoftApiRequestAllItems.call as jest.Mock).mockResolvedValue([
|
||||
{ id: 'subscription123', notificationUrl: 'https://webhook.url' },
|
||||
]);
|
||||
(microsoftApiRequest.call as jest.Mock).mockRejectedValue(new Error('API request failed'));
|
||||
mockWebhookFunctions.getNodeWebhookUrl.mockReturnValue('https://webhook.url');
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
node: {
|
||||
subscriptionIds: ['subscription123'],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhookMethods.default.delete.call(
|
||||
mockWebhookFunctions,
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('webhook', () => {
|
||||
it('should handle Microsoft Graph validation request correctly', async () => {
|
||||
const mockRequest = {
|
||||
query: {
|
||||
validationToken: 'validation-token',
|
||||
},
|
||||
};
|
||||
const mockResponse = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue(mockRequest);
|
||||
mockWebhookFunctions.getResponseObject.mockReturnValue(mockResponse);
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhook.call(mockWebhookFunctions);
|
||||
expect(mockResponse.status).toHaveBeenCalledWith(200);
|
||||
expect(mockResponse.send).toHaveBeenCalledWith('validation-token');
|
||||
expect(result.noWebhookResponse).toBe(true);
|
||||
});
|
||||
|
||||
it('should process incoming event notifications', async () => {
|
||||
const mockRequest = {
|
||||
body: {
|
||||
value: [{ resourceData: { message: 'test message' } }],
|
||||
},
|
||||
query: {},
|
||||
};
|
||||
const mockResponse = {
|
||||
status: jest.fn().mockReturnThis(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue(mockRequest);
|
||||
mockWebhookFunctions.getResponseObject.mockReturnValue(mockResponse);
|
||||
|
||||
const result = await new MicrosoftTeamsTrigger().webhook.call(mockWebhookFunctions);
|
||||
|
||||
expect(result.workflowData).toEqual([
|
||||
[
|
||||
{
|
||||
json: { message: 'test message' },
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
fetchAllTeams,
|
||||
fetchAllChannels,
|
||||
createSubscription,
|
||||
getResourcePath,
|
||||
} from '../../v2/helpers/utils-trigger';
|
||||
import { microsoftApiRequest } from '../../v2/transport';
|
||||
|
||||
jest.mock('../../v2/transport', () => ({
|
||||
microsoftApiRequest: {
|
||||
call: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Microsoft Teams Helpers Functions', () => {
|
||||
let mockLoadOptionsFunctions: any;
|
||||
let mockHookFunctions: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLoadOptionsFunctions = mock();
|
||||
mockHookFunctions = mock();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('fetchAllTeams', () => {
|
||||
it('should fetch all teams and map them correctly', async () => {
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValue({
|
||||
value: [
|
||||
{ id: 'team1', displayName: 'Team 1' },
|
||||
{ id: 'team2', displayName: 'Team 2' },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await fetchAllTeams.call(mockLoadOptionsFunctions);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 'team1', displayName: 'Team 1' },
|
||||
{ id: 'team2', displayName: 'Team 2' },
|
||||
]);
|
||||
expect(microsoftApiRequest.call).toHaveBeenCalledWith(
|
||||
mockLoadOptionsFunctions,
|
||||
'GET',
|
||||
'/v1.0/me/joinedTeams',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if getTeams fails', async () => {
|
||||
(microsoftApiRequest.call as jest.Mock).mockRejectedValue(new Error('Failed to fetch teams'));
|
||||
|
||||
await expect(fetchAllTeams.call(mockLoadOptionsFunctions)).rejects.toThrow(
|
||||
'Failed to fetch teams',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAllChannels', () => {
|
||||
it('should fetch all channels for a team and map them correctly', async () => {
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValue({
|
||||
value: [
|
||||
{ id: 'channel1', displayName: 'Channel 1' },
|
||||
{ id: 'channel2', displayName: 'Channel 2' },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await fetchAllChannels.call(mockLoadOptionsFunctions, 'team1');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 'channel1', displayName: 'Channel 1' },
|
||||
{ id: 'channel2', displayName: 'Channel 2' },
|
||||
]);
|
||||
expect(microsoftApiRequest.call).toHaveBeenCalledWith(
|
||||
mockLoadOptionsFunctions,
|
||||
'GET',
|
||||
'/v1.0/teams/team1/channels',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if getChannels fails', async () => {
|
||||
(microsoftApiRequest.call as jest.Mock).mockRejectedValue(
|
||||
new Error('Failed to fetch channels'),
|
||||
);
|
||||
|
||||
await expect(fetchAllChannels.call(mockLoadOptionsFunctions, 'team1')).rejects.toThrow(
|
||||
'Failed to fetch channels',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSubscription', () => {
|
||||
it('should create a subscription and return the subscription ID', async () => {
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValue({
|
||||
id: 'subscription123',
|
||||
resource: '/resource/path',
|
||||
notificationUrl: 'https://webhook.url',
|
||||
expirationDateTime: '2024-01-01T00:00:00Z',
|
||||
});
|
||||
|
||||
const result = await createSubscription.call(
|
||||
mockHookFunctions,
|
||||
'https://webhook.url',
|
||||
'/resource/path',
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'subscription123',
|
||||
resource: '/resource/path',
|
||||
notificationUrl: 'https://webhook.url',
|
||||
expirationDateTime: '2024-01-01T00:00:00Z',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw a NodeApiError if the API request fails', async () => {
|
||||
const error = new NodeApiError(mockHookFunctions.getNode(), {
|
||||
message: 'API request failed',
|
||||
httpCode: '400',
|
||||
});
|
||||
(microsoftApiRequest.call as jest.Mock).mockRejectedValue(error);
|
||||
|
||||
await expect(
|
||||
createSubscription.call(mockHookFunctions, 'https://webhook.url', '/resource/path'),
|
||||
).rejects.toThrow(NodeApiError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getResourcePath', () => {
|
||||
it('should return the correct resource path for newChat event', async () => {
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChat');
|
||||
expect(result).toBe('/me/chats');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newChatMessage event with watchAllChats', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(true);
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChatMessage');
|
||||
expect(result).toBe('/me/chats/getAllMessages');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newChatMessage event with chatId', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(false).mockReturnValueOnce('chat123');
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChatMessage');
|
||||
expect(result).toBe('/chats/chat123/messages');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newChatMessage event with chatId missing', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(false);
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(undefined);
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChatMessage');
|
||||
expect(result).toBe('/chats/undefined/messages');
|
||||
});
|
||||
it('should return the correct resource path for newChannel event', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(false);
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce('team123');
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChannel');
|
||||
expect(result).toBe('/teams/team123/channels');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newChannel event with teamId missing', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(undefined);
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChannel');
|
||||
expect(result).toBe('/teams/undefined/channels');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newChannelMessage event with a specific team and channel', async () => {
|
||||
mockHookFunctions.getNodeParameter
|
||||
.mockReturnValueOnce(false)
|
||||
.mockReturnValueOnce('team123')
|
||||
.mockReturnValueOnce(false)
|
||||
.mockReturnValueOnce('channel123');
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newChannelMessage');
|
||||
expect(result).toBe('/teams/team123/channels/channel123/messages');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newTeamMember event', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(false);
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce('team123');
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newTeamMember');
|
||||
expect(result).toBe('/teams/team123/members');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newTeamMember event with teamId missing', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(undefined);
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newTeamMember');
|
||||
expect(result).toBe('/teams/undefined/members');
|
||||
});
|
||||
|
||||
it('should return the correct resource path for newTeamMember event with watchAllTeams', async () => {
|
||||
mockHookFunctions.getNodeParameter.mockReturnValueOnce(true);
|
||||
|
||||
(microsoftApiRequest.call as jest.Mock).mockResolvedValueOnce({
|
||||
value: [
|
||||
{ id: 'team1', displayName: 'Team 1' },
|
||||
{ id: 'team2', displayName: 'Team 2' },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await getResourcePath.call(mockHookFunctions, 'newTeamMember');
|
||||
expect(result).toEqual(['/teams/team1/members', '/teams/team2/members']);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user