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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
126 lines · 3.8 KB Raw History
import { describe, expect, it } from 'vitest';
import {
  DRIVE_FILE_COLLECTION,
  DRIVE_FOLDER_COLLECTION,
  driveFileLexicon,
  driveFolderLexicon,
  folderAncestry,
  folderPath,
  isValidName,
  MAX_NAME_LENGTH,
  normalizeFolderKey,
} from '../src/lexicon.js';

/** @param {Array<[string, string, string]>} rows - rkey, name, parent */
function treeOf(rows) {
  return new Map(rows.map(([rkey, name, parent]) => [rkey, { name, parent }]));
}

describe('the lexicons', () => {
  it('describe the collections they are named for', () => {
    expect(driveFileLexicon.id).toBe(DRIVE_FILE_COLLECTION);
    expect(driveFolderLexicon.id).toBe(DRIVE_FOLDER_COLLECTION);
    expect(driveFileLexicon.defs.main.record.required).toContain('parts');
    expect(driveFolderLexicon.defs.main.record.required).toContain('name');
  });

  it('key a file to its folder by record key, not by path', () => {
    const folder = driveFileLexicon.defs.main.record.properties.folder;
    expect(folder.description).toMatch(/[Rr]ecord key/);
  });
});

describe('isValidName', () => {
  it('accepts an ordinary name', () => {
    expect(isValidName('notes.txt')).toBe(true);
    expect(isValidName('a name with spaces.pdf')).toBe(true);
    expect(isValidName('résumé.pdf')).toBe(true);
  });

  it('rejects a name that is not one path segment', () => {
    expect(isValidName('a/b')).toBe(false);
    expect(isValidName('a\\b')).toBe(false);
    expect(isValidName('.')).toBe(false);
    expect(isValidName('..')).toBe(false);
  });

  it('rejects an empty, over-long, or non-string name', () => {
    expect(isValidName('')).toBe(false);
    expect(isValidName('x'.repeat(MAX_NAME_LENGTH + 1))).toBe(false);
    expect(isValidName(undefined)).toBe(false);
    expect(isValidName(42)).toBe(false);
  });

  it('rejects a control character, which would display as a shorter name', () => {
    expect(isValidName(`safe${String.fromCharCode(0)}.txt`)).toBe(false);
    expect(isValidName(`safe${String.fromCharCode(0x1f)}.txt`)).toBe(false);
    expect(isValidName(`safe${String.fromCharCode(0x7f)}.txt`)).toBe(false);
    expect(isValidName(`safe${String.fromCharCode(0x0a)}.txt`)).toBe(false);
  });
});

describe('normalizeFolderKey', () => {
  it('reads the top level from every empty form', () => {
    expect(normalizeFolderKey('')).toBe('');
    expect(normalizeFolderKey(undefined)).toBe('');
    expect(normalizeFolderKey(null)).toBe('');
  });

  it('keeps a record key as it is', () => {
    expect(normalizeFolderKey('3lfoldertid')).toBe('3lfoldertid');
  });

  it('refuses anything that is not one key', () => {
    expect(normalizeFolderKey('photos/2026')).toBe(null);
    expect(normalizeFolderKey('..')).toBe(null);
    expect(normalizeFolderKey(7)).toBe(null);
  });
});

describe('folderAncestry', () => {
  const tree = treeOf([
    ['a', 'photos', ''],
    ['b', '2026', 'a'],
    ['c', 'june', 'b'],
  ]);

  it('gives nothing for the top level', () => {
    expect(folderAncestry('', tree)).toEqual([]);
  });

  it('walks from the top level down to the folder', () => {
    expect(folderAncestry('c', tree)).toEqual([
      { rkey: 'a', name: 'photos' },
      { rkey: 'b', name: '2026' },
      { rkey: 'c', name: 'june' },
    ]);
  });

  it('reports a chain that does not resolve', () => {
    expect(folderAncestry('missing', tree)).toBe(null);
  });

  it('refuses to follow a loop rather than hanging', () => {
    const looped = treeOf([
      ['x', 'one', 'y'],
      ['y', 'two', 'x'],
    ]);
    expect(folderAncestry('x', looped)).toBe(null);
  });
});

describe('folderPath', () => {
  const tree = treeOf([
    ['a', 'photos', ''],
    ['b', '2026', 'a'],
  ]);

  it('joins the chain for display', () => {
    expect(folderPath('b', tree)).toBe('photos/2026');
    expect(folderPath('', tree)).toBe('');
  });

  it('reports a broken chain as no path', () => {
    expect(folderPath('nope', tree)).toBe(null);
  });
});