import { describe, it, expect } from 'vitest';
import { getManagedObjectTool } from '../../../src/tools/managedObjects/getManagedObject.js';
import { snapshotTest } from '../../helpers/snapshotTest.js';
import { setupTestEnvironment } from '../../helpers/testEnvironment.js';
import { server } from '../../setup.js';
import { http, HttpResponse } from 'msw';
describe('getManagedObject', () => {
const getSpy = setupTestEnvironment();
// ===== SNAPSHOT TEST =====
it('should match tool schema snapshot', async () => {
await snapshotTest('getManagedObject', getManagedObjectTool);
});
// ===== REQUEST CONSTRUCTION TESTS =====
describe('Request Construction', () => {
const requestCases = [
{
name: 'constructs URL with objectType and objectId',
input: { objectType: 'alpha_user', objectId: 'obj-123' },
assert: ({ url, scopes }: any) => {
expect(url).toBe('https://test.forgeblocks.com/openidm/managed/alpha_user/obj-123');
expect(scopes).toEqual(['fr:idm:*']);
},
},
{
name: 'passes correct scopes to auth',
input: { objectType: 'alpha_user', objectId: 'obj-123' },
assert: ({ scopes }: any) => expect(scopes).toEqual(['fr:idm:*']),
},
];
it.each(requestCases)('$name', async ({ input, assert }) => {
await getManagedObjectTool.toolFunction(input as any);
const [url, scopes] = getSpy().mock.calls.at(-1)!;
assert({ url, scopes });
});
});
// ===== RESPONSE HANDLING TESTS =====
describe('Response Handling', () => {
it('should format successful response with full object', async () => {
server.use(
http.get('https://*/openidm/managed/:objectType/:objectId', ({ request, params }) => {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new HttpResponse(
JSON.stringify({ error: 'unauthorized' }),
{ status: 401 }
);
}
return HttpResponse.json({
_id: params.objectId as string,
_rev: '1',
userName: 'test',
mail: 'test@example.com',
givenName: 'Test',
sn: 'User',
});
})
);
const result = await getManagedObjectTool.toolFunction({
objectType: 'alpha_user',
objectId: 'obj-123',
});
expect(result.content[0].text).toContain('obj-123');
expect(result.content[0].text).toContain('_rev');
expect(result.content[0].text).toContain('userName');
expect(result.content[0].type).toBe('text');
});
});
// ===== INPUT VALIDATION TESTS =====
describe('Input Validation', () => {
it('should reject empty objectType string', () => {
const schema = getManagedObjectTool.inputSchema.objectType;
expect(() => schema.parse('')).toThrow();
});
it('should accept standard object types', () => {
const schema = getManagedObjectTool.inputSchema.objectType;
expect(() => schema.parse('alpha_user')).not.toThrow();
expect(() => schema.parse('bravo_role')).not.toThrow();
});
it('should accept any non-empty object type string', () => {
const schema = getManagedObjectTool.inputSchema.objectType;
expect(() => schema.parse('alpha_device')).not.toThrow();
expect(() => schema.parse('custom_application')).not.toThrow();
});
it.each([
{ name: 'rejects path traversal ../../../etc/passwd', value: '../../../etc/passwd', matcher: /path traversal/ },
{ name: 'rejects path traversal ../../admin', value: '../../admin', matcher: /path traversal/ },
{ name: 'rejects path traversal obj/../admin', value: 'obj/../admin', matcher: /path traversal/ },
{ name: 'rejects forward slash obj/123', value: 'obj/123', matcher: /path traversal/ },
{ name: 'rejects absolute path /etc/passwd', value: '/etc/passwd', matcher: /path traversal/ },
{ name: 'rejects forward traversal obj/../../admin', value: 'obj/../../admin', matcher: /path traversal/ },
{ name: 'rejects backslash obj\\123', value: 'obj\\123', matcher: /path traversal/ },
{ name: 'rejects backslash obj\\..\\admin', value: 'obj\\..\\admin', matcher: /path traversal/ },
{ name: 'rejects backslash ..\\..\\admin', value: '..\\..\\admin', matcher: /path traversal/ },
{ name: 'rejects URL-encoded obj%2e%2e', value: 'obj%2e%2e', matcher: /path traversal/ },
{ name: 'rejects URL-encoded %2e%2e%2fadmin', value: '%2e%2e%2fadmin', matcher: /path traversal/ },
{ name: 'rejects URL-encoded obj%2f123', value: 'obj%2f123', matcher: /path traversal/ },
{ name: 'rejects URL-encoded obj%5c123', value: 'obj%5c123', matcher: /path traversal/ },
{ name: 'rejects empty objectId', value: '', matcher: /cannot be empty/ },
{ name: 'rejects whitespace objectId', value: ' ', matcher: /cannot be empty or whitespace/ },
])('$name', ({ value, matcher }) => {
const schema = getManagedObjectTool.inputSchema.objectId;
expect(() => schema.parse(value)).toThrow(matcher);
});
it('should accept valid objectId', () => {
const schema = getManagedObjectTool.inputSchema.objectId;
expect(() => schema.parse('valid-object-123')).not.toThrow();
expect(() => schema.parse('obj_test')).not.toThrow();
expect(() => schema.parse('abc-123-xyz_456')).not.toThrow();
expect(() => schema.parse('uuid-1234-5678-90ab-cdef')).not.toThrow();
});
});
// ===== ERROR HANDLING TESTS =====
describe('Error Handling', () => {
it.each([
{
name: 'handles 401 Unauthorized error',
status: 401,
body: { error: 'unauthorized', message: 'Invalid token' },
matcher: /401|[Uu]nauthorized/,
},
{
name: 'handles 404 Not Found error',
status: 404,
body: { error: 'not_found', message: 'Object does not exist' },
matcher: /404|[Nn]ot [Ff]ound/,
},
])('$name', async ({ status, body, matcher }) => {
server.use(
http.get('https://*/openidm/managed/:objectType/:objectId', () => {
return new HttpResponse(JSON.stringify(body), { status });
})
);
const result = await getManagedObjectTool.toolFunction({
objectType: 'alpha_user',
objectId: status === 404 ? 'nonexistent' : 'obj-123',
});
expect(result.content[0].text).toContain('Failed to retrieve managed object');
expect(result.content[0].text).toMatch(matcher);
});
});
});