WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
1import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2import { getBacklinks } from '../../src/lib/constellation.js';
3
4const mockFetch = vi.fn();
5
6beforeEach(() => {
7 mockFetch.mockClear();
8 vi.stubGlobal('fetch', mockFetch);
9});
10
11afterEach(() => {
12 vi.unstubAllGlobals();
13});
14
15describe('getBacklinks', () => {
16 it('should return records from constellation', async () => {
17 mockFetch.mockResolvedValue({
18 ok: true,
19 json: async () => ({
20 total: 2,
21 linking_records: [
22 { did: 'did:plc:abc', collection: 'sh.tangled.repo.issue', rkey: 'rkey1' },
23 { did: 'did:plc:def', collection: 'sh.tangled.repo.issue', rkey: 'rkey2' },
24 ],
25 cursor: null,
26 }),
27 });
28
29 const result = await getBacklinks(
30 'at://did:plc:owner/sh.tangled.repo/my-repo',
31 'sh.tangled.repo.issue',
32 '.repo'
33 );
34
35 expect(result.total).toBe(2);
36 expect(result.records).toHaveLength(2);
37 expect(result.records[0]).toEqual({
38 did: 'did:plc:abc',
39 collection: 'sh.tangled.repo.issue',
40 rkey: 'rkey1',
41 });
42 expect(result.cursor).toBeNull();
43
44 expect(mockFetch).toHaveBeenCalledWith(
45 'https://constellation.microcosm.blue/links?target=at%3A%2F%2Fdid%3Aplc%3Aowner%2Fsh.tangled.repo%2Fmy-repo&collection=sh.tangled.repo.issue&path=.repo&limit=100'
46 );
47 });
48
49 it('should pass cursor and limit params', async () => {
50 mockFetch.mockResolvedValue({
51 ok: true,
52 json: async () => ({ total: 0, linking_records: [], cursor: null }),
53 });
54
55 await getBacklinks(
56 'at://did:plc:owner/sh.tangled.repo/my-repo',
57 'sh.tangled.repo.issue',
58 '.repo',
59 50,
60 'abc123'
61 );
62
63 const calledUrl = mockFetch.mock.calls[0][0] as string;
64 expect(calledUrl).toContain('limit=50');
65 expect(calledUrl).toContain('cursor=abc123');
66 });
67
68 it('should return cursor for pagination', async () => {
69 mockFetch.mockResolvedValue({
70 ok: true,
71 json: async () => ({
72 total: 200,
73 linking_records: [
74 { did: 'did:plc:abc', collection: 'sh.tangled.repo.issue', rkey: 'rkey1' },
75 ],
76 cursor: 'nextpage',
77 }),
78 });
79
80 const result = await getBacklinks(
81 'at://did:plc:owner/sh.tangled.repo/my-repo',
82 'sh.tangled.repo.issue',
83 '.repo',
84 1
85 );
86
87 expect(result.cursor).toBe('nextpage');
88 });
89
90 it('should throw on non-OK response', async () => {
91 mockFetch.mockResolvedValue({
92 ok: false,
93 status: 503,
94 statusText: 'Service Unavailable',
95 });
96
97 await expect(
98 getBacklinks('at://did:plc:owner/sh.tangled.repo/my-repo', 'sh.tangled.repo.issue', '.repo')
99 ).rejects.toThrow('Constellation API error: 503 Service Unavailable');
100 });
101
102 it('should return empty records when none found', async () => {
103 mockFetch.mockResolvedValue({
104 ok: true,
105 json: async () => ({ total: 0, linking_records: [], cursor: null }),
106 });
107
108 const result = await getBacklinks(
109 'at://did:plc:owner/sh.tangled.repo/my-repo',
110 'sh.tangled.repo.issue',
111 '.repo'
112 );
113
114 expect(result.total).toBe(0);
115 expect(result.records).toEqual([]);
116 });
117});