136 lines
3.7 KiB
TypeScript
136 lines
3.7 KiB
TypeScript
/**
|
|
* Unit tests for Slack Incoming Webhook notification
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { sendSlackNotification, getSlackWebhookUrl } from '../shared/utils/slackWebhook.js';
|
|
|
|
describe('sendSlackNotification', () => {
|
|
const webhookUrl = 'https://hooks.slack.com/services/T00/B00/xxx';
|
|
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('should send POST request with correct payload', async () => {
|
|
// Given
|
|
const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
// When
|
|
await sendSlackNotification(webhookUrl, 'Hello from TAKT');
|
|
|
|
// Then
|
|
expect(mockFetch).toHaveBeenCalledOnce();
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
webhookUrl,
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ text: 'Hello from TAKT' }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should include AbortSignal for timeout', async () => {
|
|
// Given
|
|
const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
|
|
// When
|
|
await sendSlackNotification(webhookUrl, 'test');
|
|
|
|
// Then
|
|
const callArgs = mockFetch.mock.calls[0]![1] as RequestInit;
|
|
expect(callArgs.signal).toBeInstanceOf(AbortSignal);
|
|
});
|
|
|
|
it('should write to stderr on non-ok response', async () => {
|
|
// Given
|
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
});
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
const stderrSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(true);
|
|
|
|
// When
|
|
await sendSlackNotification(webhookUrl, 'test');
|
|
|
|
// Then: no exception thrown, error written to stderr
|
|
expect(stderrSpy).toHaveBeenCalledWith(
|
|
'Slack webhook failed: HTTP 403 Forbidden\n',
|
|
);
|
|
});
|
|
|
|
it('should write to stderr on fetch error without throwing', async () => {
|
|
// Given
|
|
const mockFetch = vi.fn().mockRejectedValue(new Error('network timeout'));
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
const stderrSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(true);
|
|
|
|
// When
|
|
await sendSlackNotification(webhookUrl, 'test');
|
|
|
|
// Then: no exception thrown, error written to stderr
|
|
expect(stderrSpy).toHaveBeenCalledWith(
|
|
'Slack webhook error: network timeout\n',
|
|
);
|
|
});
|
|
|
|
it('should handle non-Error thrown values', async () => {
|
|
// Given
|
|
const mockFetch = vi.fn().mockRejectedValue('string error');
|
|
vi.stubGlobal('fetch', mockFetch);
|
|
const stderrSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(true);
|
|
|
|
// When
|
|
await sendSlackNotification(webhookUrl, 'test');
|
|
|
|
// Then
|
|
expect(stderrSpy).toHaveBeenCalledWith(
|
|
'Slack webhook error: string error\n',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getSlackWebhookUrl', () => {
|
|
const envKey = 'TAKT_NOTIFY_WEBHOOK';
|
|
let originalValue: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
originalValue = process.env[envKey];
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalValue === undefined) {
|
|
delete process.env[envKey];
|
|
} else {
|
|
process.env[envKey] = originalValue;
|
|
}
|
|
});
|
|
|
|
it('should return the webhook URL when environment variable is set', () => {
|
|
// Given
|
|
process.env[envKey] = 'https://hooks.slack.com/services/T00/B00/xxx';
|
|
|
|
// When
|
|
const url = getSlackWebhookUrl();
|
|
|
|
// Then
|
|
expect(url).toBe('https://hooks.slack.com/services/T00/B00/xxx');
|
|
});
|
|
|
|
it('should return undefined when environment variable is not set', () => {
|
|
// Given
|
|
delete process.env[envKey];
|
|
|
|
// When
|
|
const url = getSlackWebhookUrl();
|
|
|
|
// Then
|
|
expect(url).toBeUndefined();
|
|
});
|
|
});
|