astro-discovery/tests/webfinger.test.ts
Ryan Malloy 25ad52e68b test: add comprehensive WebFinger tests (17 tests)
Added complete test coverage for WebFinger JRD generation:

## Static Resources Tests (11 tests)
- Unknown resource returns null
- Basic JRD generation
- Subject defaults to resource when not provided
- Aliases support
- Properties support (URI-based names)
- Links support with rel, href, type
- Rel filtering (single and multiple)
- Link titles with language tags
- Complete JRD with all fields

## Protocol Support Tests
- HTTP/HTTPS resource URIs
- ActivityPub profile discovery (self rel, application/activity+json)
- OpenID Connect issuer discovery

## Edge Cases (4 tests)
- Resource with no links
- Empty aliases array omitted
- Empty properties object omitted
- All links filtered out by rel

Test suite now at 89 tests (up from 72), all passing.
Coverage includes RFC 7033 compliance, common use cases (Mastodon/ActivityPub,
OpenID), and edge cases.
2025-11-03 09:06:47 -07:00

481 lines
13 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { generateWebFingerJRD } from '../src/generators/webfinger.js';
import type { WebFingerConfig } from '../src/types.js';
describe('generateWebFingerJRD', () => {
const testURL = new URL('https://example.com');
describe('Static Resources', () => {
it('returns null for unknown resource', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
subject: 'acct:alice@example.com',
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:bob@example.com',
undefined,
testURL
);
expect(result).toBeNull();
});
it('generates basic JRD for known resource', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
subject: 'acct:alice@example.com',
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
expect(result).toBeTruthy();
const jrd = JSON.parse(result!);
expect(jrd.subject).toBe('acct:alice@example.com');
});
it('uses resource as subject when subject not provided', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.subject).toBe('acct:alice@example.com');
});
it('includes aliases when provided', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
aliases: [
'https://example.com/~alice',
'https://example.com/users/alice',
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.aliases).toEqual([
'https://example.com/~alice',
'https://example.com/users/alice',
]);
});
it('includes properties when provided', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
properties: {
'http://schema.org/name': 'Alice Smith',
'http://example.com/role': 'Developer',
},
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.properties).toEqual({
'http://schema.org/name': 'Alice Smith',
'http://example.com/role': 'Developer',
});
});
it('includes links when provided', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
links: [
{
rel: 'http://webfinger.net/rel/profile-page',
href: 'https://example.com/~alice',
type: 'text/html',
},
{
rel: 'http://webfinger.net/rel/avatar',
href: 'https://example.com/avatars/alice.jpg',
type: 'image/jpeg',
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links).toHaveLength(2);
expect(jrd.links[0].rel).toBe('http://webfinger.net/rel/profile-page');
expect(jrd.links[0].href).toBe('https://example.com/~alice');
expect(jrd.links[1].rel).toBe('http://webfinger.net/rel/avatar');
});
it('filters links by rel when requested', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
links: [
{
rel: 'http://webfinger.net/rel/profile-page',
href: 'https://example.com/~alice',
},
{
rel: 'http://webfinger.net/rel/avatar',
href: 'https://example.com/avatars/alice.jpg',
},
{
rel: 'http://openid.net/specs/connect/1.0/issuer',
href: 'https://auth.example.com',
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
['http://webfinger.net/rel/profile-page'],
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links).toHaveLength(1);
expect(jrd.links[0].rel).toBe('http://webfinger.net/rel/profile-page');
});
it('filters links by multiple rels', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
links: [
{ rel: 'profile', href: 'https://example.com/~alice' },
{ rel: 'avatar', href: 'https://example.com/avatars/alice.jpg' },
{ rel: 'blog', href: 'https://blog.example.com/alice' },
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
['profile', 'blog'],
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links).toHaveLength(2);
expect(jrd.links[0].rel).toBe('profile');
expect(jrd.links[1].rel).toBe('blog');
});
it('includes link titles when provided', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
links: [
{
rel: 'http://webfinger.net/rel/profile-page',
href: 'https://example.com/~alice',
titles: {
en: 'Alice Smith Profile',
es: 'Perfil de Alice Smith',
},
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links[0].titles).toEqual({
en: 'Alice Smith Profile',
es: 'Perfil de Alice Smith',
});
});
it('generates complete JRD with all fields', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
subject: 'acct:alice@example.com',
aliases: ['https://example.com/~alice'],
properties: {
'http://schema.org/name': 'Alice Smith',
},
links: [
{
rel: 'http://webfinger.net/rel/profile-page',
href: 'https://example.com/~alice',
type: 'text/html',
titles: { en: 'Profile' },
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.subject).toBe('acct:alice@example.com');
expect(jrd.aliases).toEqual(['https://example.com/~alice']);
expect(jrd.properties).toEqual({
'http://schema.org/name': 'Alice Smith',
});
expect(jrd.links).toHaveLength(1);
expect(jrd.links[0].rel).toBe('http://webfinger.net/rel/profile-page');
});
});
describe('HTTP Resource URIs', () => {
it('supports https:// resource URIs', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'https://example.com/user/alice',
links: [
{
rel: 'http://webfinger.net/rel/profile-page',
href: 'https://example.com/user/alice',
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'https://example.com/user/alice',
undefined,
testURL
);
expect(result).toBeTruthy();
const jrd = JSON.parse(result!);
expect(jrd.subject).toBe('https://example.com/user/alice');
});
});
describe('ActivityPub / Fediverse', () => {
it('supports ActivityPub profile discovery', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
aliases: ['https://example.com/@alice'],
links: [
{
rel: 'self',
type: 'application/activity+json',
href: 'https://example.com/users/alice',
},
{
rel: 'http://webfinger.net/rel/profile-page',
type: 'text/html',
href: 'https://example.com/@alice',
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links).toHaveLength(2);
expect(jrd.links[0].type).toBe('application/activity+json');
expect(jrd.links[0].rel).toBe('self');
});
});
describe('OpenID Connect', () => {
it('supports OpenID Connect issuer discovery', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
links: [
{
rel: 'http://openid.net/specs/connect/1.0/issuer',
href: 'https://auth.example.com',
},
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
['http://openid.net/specs/connect/1.0/issuer'],
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links).toHaveLength(1);
expect(jrd.links[0].rel).toBe(
'http://openid.net/specs/connect/1.0/issuer'
);
expect(jrd.links[0].href).toBe('https://auth.example.com');
});
});
describe('Edge Cases', () => {
it('returns valid JSON for resource with no links', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.subject).toBe('acct:alice@example.com');
expect(jrd.links).toBeUndefined();
});
it('omits empty aliases array', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
aliases: [],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.aliases).toBeUndefined();
});
it('omits empty properties object', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
properties: {},
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
undefined,
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.properties).toBeUndefined();
});
it('returns null when all links filtered out by rel', async () => {
const config: WebFingerConfig = {
resources: [
{
resource: 'acct:alice@example.com',
links: [
{ rel: 'profile', href: 'https://example.com/~alice' },
],
},
],
};
const result = await generateWebFingerJRD(
config,
'acct:alice@example.com',
['avatar'], // Requesting rel that doesn't exist
testURL
);
const jrd = JSON.parse(result!);
expect(jrd.links).toBeUndefined();
});
});
});