Skip to content

Commit 8676abf

Browse files
tchapacanishymko
andauthored
test: add tests for RestTransportHandler (#280)
# Description Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Follow the [`CONTRIBUTING` Guide](https://github.com/google-a2a/a2a-js/blob/main/CONTRIBUTING.md). - [x] Make your Pull Request title in the <https://www.conventionalcommits.org/> specification. - Important Prefixes for [release-please](https://github.com/googleapis/release-please): - `fix:` which represents bug fixes, and correlates to a [SemVer](https://semver.org/) patch. - `feat:` represents a new feature, and correlates to a SemVer minor. - `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking change (indicated by the `!`) and will result in a SemVer major. - [x] Ensure the tests and linter pass - [ ] Appropriate docs were updated (if necessary) Fixes #278 🦕 Co-authored-by: Ivan Shymko <[email protected]>
1 parent 5246067 commit 8676abf

File tree

4 files changed

+587
-143
lines changed

4 files changed

+587
-143
lines changed

test/server/a2a_express_app.spec.ts renamed to test/server/express/a2a_express_app.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import {
1212
import express, { Express, NextFunction, Request, Response } from 'express';
1313
import request from 'supertest';
1414

15-
import { A2AExpressApp } from '../../src/server/express/a2a_express_app.js';
16-
import { A2ARequestHandler } from '../../src/server/request_handler/a2a_request_handler.js';
17-
import { JsonRpcTransportHandler } from '../../src/server/transports/jsonrpc/jsonrpc_transport_handler.js';
18-
import { AgentCard, JSONRPCSuccessResponse, JSONRPCErrorResponse } from '../../src/index.js';
19-
import { AGENT_CARD_PATH, HTTP_EXTENSION_HEADER } from '../../src/constants.js';
20-
import { A2AError } from '../../src/server/error.js';
21-
import { ServerCallContext } from '../../src/server/context.js';
22-
import { User, UnauthenticatedUser } from '../../src/server/authentication/user.js';
15+
import { A2AExpressApp } from '../../../src/server/express/a2a_express_app.js';
16+
import { A2ARequestHandler } from '../../../src/server/request_handler/a2a_request_handler.js';
17+
import { JsonRpcTransportHandler } from '../../../src/server/transports/jsonrpc/jsonrpc_transport_handler.js';
18+
import { AgentCard, JSONRPCSuccessResponse, JSONRPCErrorResponse } from '../../../src/index.js';
19+
import { AGENT_CARD_PATH, HTTP_EXTENSION_HEADER } from '../../../src/constants.js';
20+
import { A2AError } from '../../../src/server/error.js';
21+
import { ServerCallContext } from '../../../src/server/context.js';
22+
import { User, UnauthenticatedUser } from '../../../src/server/authentication/user.js';
2323

2424
describe('A2AExpressApp', () => {
2525
let mockRequestHandler: A2ARequestHandler;
Lines changed: 83 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { describe, it, beforeEach, afterEach, assert, expect, vi, Mock } from 'v
22
import express, { Express } from 'express';
33
import request from 'supertest';
44

5-
import { restHandler, UserBuilder } from '../../src/server/express/index.js';
6-
import { A2ARequestHandler } from '../../src/server/request_handler/a2a_request_handler.js';
7-
import { AgentCard, Task, Message } from '../../src/types.js';
8-
import { A2AError } from '../../src/server/error.js';
5+
import { restHandler, UserBuilder } from '../../../src/server/express/index.js';
6+
import { A2ARequestHandler } from '../../../src/server/request_handler/a2a_request_handler.js';
7+
import { AgentCard, Task, Message } from '../../../src/types.js';
8+
import { A2AError } from '../../../src/server/error.js';
99

1010
/**
1111
* Test suite for restHandler - HTTP+JSON/REST transport implementation
@@ -113,25 +113,13 @@ describe('restHandler', () => {
113113
});
114114

115115
describe('POST /v1/message:send', () => {
116-
it('should accept camelCase message and return 201 with Task', async () => {
116+
it.each([
117+
{ name: 'camelCase', message: testMessage },
118+
{ name: 'snake_case', message: snakeCaseMessage },
119+
])('should accept $name message and return 201 with Task', async ({ message }) => {
117120
(mockRequestHandler.sendMessage as Mock).mockResolvedValue(testTask);
118121

119-
const response = await request(app)
120-
.post('/v1/message:send')
121-
.send({ message: testMessage })
122-
.expect(201);
123-
124-
assert.deepEqual(response.body.id, testTask.id);
125-
assert.deepEqual(response.body.kind, 'task');
126-
});
127-
128-
it('should accept snake_case message and return 201 with Task', async () => {
129-
(mockRequestHandler.sendMessage as Mock).mockResolvedValue(testTask);
130-
131-
const response = await request(app)
132-
.post('/v1/message:send')
133-
.send({ message: snakeCaseMessage })
134-
.expect(201);
122+
const response = await request(app).post('/v1/message:send').send({ message }).expect(201);
135123

136124
assert.deepEqual(response.body.id, testTask.id);
137125
assert.deepEqual(response.body.kind, 'task');
@@ -160,31 +148,17 @@ describe('restHandler', () => {
160148
});
161149

162150
describe('POST /v1/message:stream', () => {
163-
it('should accept camelCase message and stream via SSE', async () => {
151+
it.each([
152+
{ name: 'camelCase', message: testMessage },
153+
{ name: 'snake_case', message: snakeCaseMessage },
154+
])('should accept $name message and stream via SSE', async ({ message }) => {
164155
async function* mockStream() {
165156
yield testMessage;
166157
yield testTask;
167158
}
168159
(mockRequestHandler.sendMessageStream as Mock).mockResolvedValue(mockStream());
169160

170-
const response = await request(app)
171-
.post('/v1/message:stream')
172-
.send({ message: testMessage })
173-
.expect(200);
174-
175-
assert.equal(response.headers['content-type'], 'text/event-stream');
176-
});
177-
178-
it('should accept snake_case message and stream via SSE', async () => {
179-
async function* mockStream() {
180-
yield testMessage;
181-
}
182-
(mockRequestHandler.sendMessageStream as Mock).mockResolvedValue(mockStream());
183-
184-
const response = await request(app)
185-
.post('/v1/message:stream')
186-
.send({ message: snakeCaseMessage })
187-
.expect(200);
161+
const response = await request(app).post('/v1/message:stream').send({ message }).expect(200);
188162

189163
assert.equal(response.headers['content-type'], 'text/event-stream');
190164
});
@@ -341,33 +315,25 @@ describe('restHandler', () => {
341315
};
342316

343317
describe('POST /v1/tasks/:taskId/pushNotificationConfigs', () => {
344-
it('should accept camelCase pushNotificationConfig and return 201', async () => {
345-
(mockRequestHandler.setTaskPushNotificationConfig as Mock).mockResolvedValue(mockConfig);
346-
347-
const response = await request(app)
348-
.post('/v1/tasks/task-1/pushNotificationConfigs')
349-
.send({
350-
pushNotificationConfig: {
351-
id: 'config-1',
352-
url: 'https://example.com/webhook',
353-
},
354-
})
355-
.expect(201);
356-
357-
assert.deepEqual(response.body.taskId, mockConfig.taskId);
358-
});
359-
360-
it('should accept snake_case push_notification_config and return 201', async () => {
318+
it.each([
319+
{
320+
name: 'camelCase',
321+
payload: {
322+
pushNotificationConfig: { id: 'config-1', url: 'https://example.com/webhook' },
323+
},
324+
},
325+
{
326+
name: 'snake_case',
327+
payload: {
328+
push_notification_config: { id: 'config-1', url: 'https://example.com/webhook' },
329+
},
330+
},
331+
])('should accept $name config and return 201', async ({ payload }) => {
361332
(mockRequestHandler.setTaskPushNotificationConfig as Mock).mockResolvedValue(mockConfig);
362333

363334
const response = await request(app)
364335
.post('/v1/tasks/task-1/pushNotificationConfigs')
365-
.send({
366-
push_notification_config: {
367-
id: 'config-1',
368-
url: 'https://example.com/webhook',
369-
},
370-
})
336+
.send(payload)
371337
.expect(201);
372338

373339
assert.deepEqual(response.body.taskId, mockConfig.taskId);
@@ -494,89 +460,73 @@ describe('restHandler', () => {
494460
* File Parts Format Tests
495461
*/
496462
describe('File parts format acceptance', () => {
497-
it('should accept camelCase mimeType in file parts', async () => {
498-
(mockRequestHandler.sendMessage as Mock).mockResolvedValue(testTask);
499-
500-
await request(app)
501-
.post('/v1/message:send')
502-
.send({
503-
message: {
504-
messageId: 'msg-file',
505-
role: 'user',
506-
kind: 'message',
507-
parts: [
508-
{
509-
kind: 'file',
510-
file: {
511-
uri: 'https://example.com/file.pdf',
512-
mimeType: 'application/pdf',
513-
name: 'document.pdf',
514-
},
463+
it.each([
464+
{
465+
name: 'camelCase',
466+
message: {
467+
messageId: 'msg-file',
468+
role: 'user',
469+
kind: 'message',
470+
parts: [
471+
{
472+
kind: 'file',
473+
file: {
474+
uri: 'https://example.com/file.pdf',
475+
mimeType: 'application/pdf',
476+
name: 'document.pdf',
515477
},
516-
],
517-
},
518-
})
519-
.expect(201);
520-
});
521-
522-
it('should accept snake_case mime_type in file parts', async () => {
478+
},
479+
],
480+
},
481+
},
482+
{
483+
name: 'snake_case',
484+
message: {
485+
message_id: 'msg-file',
486+
role: 'user',
487+
kind: 'message',
488+
parts: [
489+
{
490+
kind: 'file',
491+
file: {
492+
uri: 'https://example.com/file.pdf',
493+
mime_type: 'application/pdf',
494+
name: 'document.pdf',
495+
},
496+
},
497+
],
498+
},
499+
},
500+
])('should accept $name file parts', async ({ message }) => {
523501
(mockRequestHandler.sendMessage as Mock).mockResolvedValue(testTask);
524502

525-
await request(app)
526-
.post('/v1/message:send')
527-
.send({
528-
message: {
529-
message_id: 'msg-file',
530-
role: 'user',
531-
kind: 'message',
532-
parts: [
533-
{
534-
kind: 'file',
535-
file: {
536-
uri: 'https://example.com/file.pdf',
537-
mime_type: 'application/pdf',
538-
name: 'document.pdf',
539-
},
540-
},
541-
],
542-
},
543-
})
544-
.expect(201);
503+
await request(app).post('/v1/message:send').send({ message }).expect(201);
545504
});
546505
});
547506

548507
/**
549508
* Configuration Format Tests
550509
*/
551510
describe('Configuration format acceptance', () => {
552-
it('should accept camelCase configuration fields', async () => {
553-
(mockRequestHandler.sendMessage as Mock).mockResolvedValue(testTask);
554-
555-
await request(app)
556-
.post('/v1/message:send')
557-
.send({
511+
it.each([
512+
{
513+
name: 'camelCase',
514+
payload: {
558515
message: testMessage,
559-
configuration: {
560-
acceptedOutputModes: ['text/plain'],
561-
historyLength: 5,
562-
},
563-
})
564-
.expect(201);
565-
});
566-
567-
it('should accept snake_case configuration fields', async () => {
516+
configuration: { acceptedOutputModes: ['text/plain'], historyLength: 5 },
517+
},
518+
},
519+
{
520+
name: 'snake_case',
521+
payload: {
522+
message: snakeCaseMessage,
523+
configuration: { accepted_output_modes: ['text/plain'], history_length: 5 },
524+
},
525+
},
526+
])('should accept $name configuration fields', async ({ payload }) => {
568527
(mockRequestHandler.sendMessage as Mock).mockResolvedValue(testTask);
569528

570-
await request(app)
571-
.post('/v1/message:send')
572-
.send({
573-
message: snakeCaseMessage,
574-
configuration: {
575-
accepted_output_modes: ['text/plain'],
576-
history_length: 5,
577-
},
578-
})
579-
.expect(201);
529+
await request(app).post('/v1/message:send').send(payload).expect(201);
580530
});
581531
});
582532

0 commit comments

Comments
 (0)