import { describe, it, expect, vi, beforeEach } from 'vitest';
import { NextRequest } from 'next/server';
const mockFetchProfile = vi.fn();
const mockSanitize = vi.fn((input: string) => input);
vi.mock('@/lib/api', () => ({
fetchProfile: (id: string) => mockFetchProfile(id),
}));
vi.mock('@/lib/sanitize', () => ({
sanitize: (input: string) => mockSanitize(input),
}));
import { GET } from '@/app/api/embed/[handleOrDid]/data/route';
function buildRequest(
handleOrDid: string,
): [NextRequest, { params: Promise<{ handleOrDid: string }> }] {
const request = new NextRequest(`https://sifa.id/api/embed/${handleOrDid}/data`);
const params = Promise.resolve({ handleOrDid });
return [request, { params }];
}
const fullProfile = {
did: 'did:plc:abc123',
handle: 'alice.bsky.social',
displayName: 'Alice Smith',
avatar: 'https://cdn.example.com/alice.jpg',
headline: 'Senior Engineer at Acme',
locationCity: 'Amsterdam',
locationRegion: 'North Holland',
locationCountry: 'Netherlands',
website: 'https://alice.dev',
openTo: ['opportunities', 'mentoring'],
trustStats: [{ label: 'endorsements', value: 5 }],
verifiedAccounts: [{ platform: 'github', identifier: 'alice', url: 'https://github.com/alice' }],
claimed: true,
};
describe('GET /api/embed/[handleOrDid]/data', () => {
beforeEach(() => {
vi.clearAllMocks();
mockSanitize.mockImplementation((input: string) => input);
});
it('returns card data for a valid profile', async () => {
mockFetchProfile.mockResolvedValue(fullProfile);
const [request, context] = buildRequest('alice.bsky.social');
const response = await GET(request, context);
const body = await response.json();
expect(response.status).toBe(200);
expect(body.did).toBe('did:plc:abc123');
expect(body.handle).toBe('alice.bsky.social');
expect(body.displayName).toBe('Alice Smith');
expect(body.headline).toBe('Senior Engineer at Acme');
expect(body.location).toEqual({
country: 'Netherlands',
countryCode: undefined,
region: 'North Holland',
city: 'Amsterdam',
});
expect(body.profileUrl).toBe('https://sifa.id/p/alice.bsky.social');
const cacheControl = response.headers.get('Cache-Control');
expect(cacheControl).toContain('max-age=3600');
const cors = response.headers.get('Access-Control-Allow-Origin');
expect(cors).toBe('*');
});
it('returns 404 for unknown profile', async () => {
mockFetchProfile.mockResolvedValue(null);
const [request, context] = buildRequest('nobody.bsky.social');
const response = await GET(request, context);
const body = await response.json();
expect(response.status).toBe(404);
expect(body.error).toBe('Profile not found');
});
it('sanitizes display name and headline', async () => {
mockFetchProfile.mockResolvedValue({
...fullProfile,
displayName: '',
headline: '
',
});
const [request, context] = buildRequest('alice.bsky.social');
await GET(request, context);
expect(mockSanitize).toHaveBeenCalledWith('');
expect(mockSanitize).toHaveBeenCalledWith('
');
});
it('handles missing optional fields gracefully', async () => {
mockFetchProfile.mockResolvedValue({
did: 'did:plc:minimal',
handle: 'minimal.bsky.social',
claimed: false,
});
const [request, context] = buildRequest('minimal.bsky.social');
const response = await GET(request, context);
const body = await response.json();
expect(response.status).toBe(200);
expect(body.avatar).toBeNull();
expect(body.displayName).toBeNull();
expect(body.headline).toBeNull();
expect(body.location).toBeNull();
expect(body.website).toBeNull();
expect(body.openTo).toEqual([]);
expect(body.trustStats).toEqual([]);
expect(body.verifiedAccounts).toEqual([]);
});
});