~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
207 lines · 6.9 KB Raw History
/**
 * Showing a commit author as the identity the hosting account claims: an
 * atproto profile where the claim names a DID, and a label with a face where
 * it names something that holds no account.
 */

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { describeAuthor, loadAuthors } from '../src/lib/authors.js';
import { account } from '../src/lib/git.js';

const OWNER = 'did:plc:owner';
const FRIEND = 'did:plc:friend';
const AVATAR_CID = 'bafkreiavatar';

/**
 * @param {Array<Record<string, unknown>>} idents
 * @param {Array<Record<string, unknown>>} profiles
 */
function serve(idents, profiles) {
  globalThis.fetch = async (/** @type {string|URL|Request} */ input) => {
    const url = String(input);
    if (url.includes('com.atproto.repo.getRecord')) {
      return Response.json({
        uri: `at://${OWNER}/dev.pdsjs.git.identity/self`,
        value: {
          $type: 'dev.pdsjs.git.identity',
          idents,
          createdAt: '2026-01-01T00:00:00.000Z',
        },
      });
    }
    if (url.includes('app.bsky.actor.getProfiles')) {
      const wanted = new URL(url).searchParams.getAll('actors');
      return Response.json({
        profiles: profiles.filter((p) => wanted.includes(String(p.did))),
      });
    }
    return Response.json({ error: 'NotFound' }, { status: 404 });
  };
}

describe('describing a commit author', () => {
  const original = globalThis.fetch;
  const originalLocation = globalThis.location;

  beforeEach(() => {
    account.did = OWNER;
    account.pds = 'https://pds.example';
    account.handle = 'owner.example';
    // The git service decision probes this page's own origin first; the
    // stubs below refuse that probe, so the account's PDS stays the service.
    globalThis.location = new URL('https://site.example/');
  });

  afterEach(() => {
    globalThis.fetch = original;
    globalThis.location = originalLocation;
    account.did = '';
    account.pds = '';
    account.handle = '';
  });

  it('takes the claims and faces the server resolves, where it serves them', async () => {
    /** @type {string[]} */
    const asked = [];
    globalThis.fetch = async (/** @type {string|URL|Request} */ input) => {
      const url = String(input);
      asked.push(url);
      if (url.includes('community.lexicon.service.describe')) {
        return Response.json({
          methods: [{ value: 'dev.pdsjs.git.getAuthors' }],
        });
      }
      if (url.includes('dev.pdsjs.git.getAuthors')) {
        return Response.json({
          owner: {
            did: OWNER,
            handle: 'owner.example',
            displayName: 'The Account',
            avatar: 'https://cdn/owner.jpg',
          },
          claims: [
            {
              name: '',
              email: 'friend@example.com',
              label: '',
              did: FRIEND,
              avatar: '',
              profile: {
                did: FRIEND,
                handle: 'friend.example',
                displayName: 'A Friend',
                avatar: 'https://cdn/friend.jpg',
              },
            },
            {
              name: '',
              email: 'noreply@anthropic.com',
              label: 'Claude',
              did: '',
              avatar: 'https://pds.example/xrpc/com.atproto.sync.getBlob?cid=x',
              profile: null,
            },
          ],
        });
      }
      return Response.json({ error: 'NotFound' }, { status: 404 });
    };
    await loadAuthors();

    const friend = describeAuthor('Friend <friend@example.com>');
    expect(friend.name).toBe('A Friend');
    expect(friend.avatar).toBe('https://cdn/friend.jpg');
    expect(friend.atproto).toBe(true);
    const tool = describeAuthor('Claude <noreply@anthropic.com>');
    expect(tool.name).toBe('Claude');
    expect(tool.avatar).toContain('getBlob?cid=x');
    expect(asked.some((url) => url.includes('getProfiles'))).toBe(false);
    expect(asked.some((url) => url.includes('repo.getRecord'))).toBe(false);
  });

  it('names a labelled identity itself, not the account holding the claim', async () => {
    // The account has a profile of its own, and the claim carries no DID.
    // Reading the account's profile here would show every commit the tool
    // co-wrote under the account's own name and face.
    serve(
      [
        {
          email: 'noreply@anthropic.com',
          label: 'Claude',
          avatar: { $type: 'blob', ref: { $link: AVATAR_CID } },
        },
      ],
      [{ did: OWNER, handle: 'owner.example', displayName: 'The Account' }],
    );
    await loadAuthors();

    const author = describeAuthor('Claude <noreply@anthropic.com>');
    expect(author.name).toBe('Claude');
    expect(author.atproto).toBe(false);
    expect(author.handle).toBe('');
    expect(author.avatar).toContain(AVATAR_CID);
    expect(author.avatar).toContain(encodeURIComponent(OWNER));
  });

  it('shows the profile of a claim that names a DID', async () => {
    serve(
      [{ email: 'friend@example.com', did: FRIEND }],
      [
        { did: OWNER, handle: 'owner.example', displayName: 'The Account' },
        {
          did: FRIEND,
          handle: 'friend.example',
          displayName: 'A Friend',
          avatar: 'https://cdn.example/friend.jpg',
        },
      ],
    );
    await loadAuthors();

    const author = describeAuthor('Friend <friend@example.com>');
    expect(author.name).toBe('A Friend');
    expect(author.handle).toBe('friend.example');
    expect(author.atproto).toBe(true);
    expect(author.did).toBe(FRIEND);
  });

  it('carries the DID of a claimed account that publishes no profile', async () => {
    // The DID is what a face with no picture draws its pattern from, so
    // dropping it here gave one person an identicon beside their words and a
    // bare letter on their commits.
    serve(
      [{ email: 'friend@example.com', did: FRIEND }],
      [{ did: OWNER, handle: 'owner.example', displayName: 'The Account' }],
    );
    await loadAuthors();

    const author = describeAuthor('Friend <friend@example.com>');
    expect(author.did).toBe(FRIEND);
    expect(author.atproto).toBe(true);
    // Nothing said what to call them, so the commit's own name stands.
    expect(author.name).toBe('Friend');
    expect(author.avatar).toBe('');
  });

  it('reads a claim with neither DID nor label as the account itself', async () => {
    serve(
      [{ email: 'owner@example.com' }],
      [{ did: OWNER, handle: 'owner.example', displayName: 'The Account' }],
    );
    await loadAuthors();

    const author = describeAuthor('Owner <owner@example.com>');
    expect(author.name).toBe('The Account');
    expect(author.atproto).toBe(true);
  });

  it('leaves an unclaimed author as the name the commit carries', async () => {
    serve([{ email: 'owner@example.com' }], []);
    await loadAuthors();

    const author = describeAuthor('Stranger <stranger@example.com>');
    expect(author.name).toBe('Stranger');
    expect(author.atproto).toBe(false);
    expect(author.avatar).toBe('');
  });
});