char-slop/stars.hackery.site
git clone https://git.t4t.associates/char-slop/stars.hackery.site
2550ded
main
1import { assert , assertEquals } from "@std/assert" ; 2import { DatabaseSync } from "node:sqlite" ; 3import { Auth } from "./auth.ts" ; 4import { Hydrant } from "./hydrant.ts" ; 5import { handler } from "./main.ts" ; 6import { Follow , Star , starUrl } from "./records.ts" ; 7import { Index } from "./sync.ts" ; 8import { TitleLookup } from "./title.ts" ; 9 10const did = "did:plc:abcdefghij" ; 11const other = "did:plc:zyxwvutsrq" ; 12 13Deno . test ( "record schemas and link rules" , () => { 14const star = Star . build ({ 15url :"https://example.org/post" , 16comment :"hello" , 17createdAt :new Date (). toISOString (), 18}); 19assert ( Star . validate ( star ). success ); 20assert ( ! Star . validate ({ ...star , comment :"🧡" . repeat ( 32769 ) }). success ); 21assertEquals ( starUrl ( "javascript:alert(1)" ), undefined ); 22assertEquals ( starUrl ( "https://username@example.org" ), undefined ); 23assert ( 24Follow . validate ( 25Follow . build ({ subject :other , createdAt :new Date (). toISOString () }), 26). success , 27); 28}); 29 30Deno . test ( "index feeds follow subjects, handles duplicate follows and deletions" , () => { 31const db = new DatabaseSync ( ":memory:" ); 32try { 33new Auth ( db , new URL ( "http://127.0.0.1:8000" )); 34const index = new Index ( db , new Hydrant ( new URL ( "http://127.0.0.1:3000" ))); 35const s = ( author :string , tid :string ) => 36`at:// ${ author } / ${ Star . id } / ${ tid } ` ; 37const follow = ( tid :string ) => `at:// ${ did } / ${ Follow . id } / ${ tid } ` ; 38const record = Star . build ({ 39url :"https://example.org/" , 40createdAt :new Date (). toISOString (), 41}); 42index . star ( s ( other , "3mgabcdefghij" ), record ); 43index . star ( s ( did , "3mgabcdefghik" ), record ); 44assertEquals ( index . page ( did ). stars . length , 1 ); 45const f = Follow . build ({ 46subject :other , 47createdAt :new Date (). toISOString (), 48}); 49index . follow ( follow ( "3mgabcdefghij" ), f ); 50index . follow ( follow ( "3mgabcdefghik" ), f ); 51assertEquals ( index . page ( did ). stars . length , 2 ); 52index . removeFollow ( follow ( "3mgabcdefghij" )); 53assertEquals ( index . page ( did ). stars . length , 2 ); 54index . removeFollow ( follow ( "3mgabcdefghik" )); 55assertEquals ( index . page ( did ). stars . length , 1 ); 56index . removeStar ( s ( did , "3mgabcdefghik" )); 57assertEquals ( index . page ( did ). stars . length , 0 ); 58} finally { 59db . close (); 60} 61}); 62 63Deno . test ( "an empty follow page completes reconciliation" , async () => { 64const db = new DatabaseSync ( ":memory:" ); 65try { 66new Auth ( db , new URL ( "http://127.0.0.1:8000" )); 67const hydrant = new Hydrant ( new URL ( "http://127.0.0.1:3000" )); 68hydrant . records = () => Promise . resolve ({ entries :[] }); 69const index = new Index ( db , hydrant ); 70await index . reconcileFollows ( did ); 71assertEquals ( index . following ( did , other ), []); 72} finally { 73db . close (); 74} 75}); 76 77Deno . test ( "anonymous pages are rendered without client scripts" , async () => { 78const db = new DatabaseSync ( ":memory:" ); 79try { 80const auth = new Auth ( db , new URL ( "http://127.0.0.1:8000" )); 81const hydrant = new Hydrant ( new URL ( "http://127.0.0.1:3000" )); 82const index = new Index ( db , hydrant ); 83const titles = new TitleLookup ( db ); 84const res = await handler ( new Request ( "http://127.0.0.1:8000/" ), { 85 auth, 86 hydrant, 87 index, 88 titles, 89}); 90assertEquals ( res . status , 200 ); 91const body = await res . text (); 92assert ( body . includes ( 'href="/login">Sign in</a>' )); 93assert ( ! body . includes ( 'action="/login"' )); 94assert ( ! body . includes ( "<script" )); 95const signIn = await handler ( 96new Request ( "http://127.0.0.1:8000/login" ), 97{ auth, hydrant, index, titles}, 98); 99assertEquals ( signIn . status , 200 ); 100assert (( await signIn . text ()). includes ( 'action="/login"' )); 101const share = await handler ( 102new Request ( "http://127.0.0.1:8000/share" ), 103{ auth, hydrant, index, titles}, 104); 105assertEquals ( share . headers . get ( "location" ), "/login" ); 106const metadata = await handler ( 107new Request ( "http://127.0.0.1:8000/oauth-client-metadata.json" ), 108{ auth, hydrant, index, titles}, 109); 110assertEquals ( metadata . status , 200 ); 111assert (( await metadata . json ()). scope . includes ( Star . id )); 112} finally { 113db . close (); 114} 115}); 116 117Deno . test ( "stream events are mirrored once and resumed from the cursor" , async () => { 118const db = new DatabaseSync ( ":memory:" ); 119const requested :string [] = []; 120const server = Deno . serve ( 121{ hostname :"127.0.0.1" , port :0 , onListen () {} }, 122( req ) => { 123requested . push ( new URL ( req . url ). searchParams . get ( "cursor" ) ! ); 124const { socket, response} = Deno . upgradeWebSocket ( req ); 125socket . onopen = () => { 126const record = { did, collection :Star . id , rkey :"3mgabcdefghij" }; 127socket . send ( JSON . stringify ({ id :7 , type :"record" , record})); 128socket . send ( JSON . stringify ({ id :8 , type :"identity" })); 129socket . close (); 130}; 131return response ; 132}, 133); 134try { 135new Auth ( db , new URL ( "http://127.0.0.1:8000" )); 136const hydrant = new Hydrant ( 137new URL ( `http://127.0.0.1: ${ server . addr . port } ` ), 138); 139hydrant . repos = async function * () {}; 140hydrant . record = ( uri ) => 141Promise . resolve ({ 142 uri, 143value :Star . build ({ 144url :"https://example.org/" , 145createdAt :new Date (). toISOString (), 146}), 147}); 148const index = new Index ( db , hydrant ); 149const stop = new AbortController (); 150const run = index . run ( stop . signal ); 151while ( requested . length < 2 ) await new Promise (( r ) => setTimeout ( r , 20 )); 152stop . abort (); 153await run ; 154assertEquals ( requested , [ "0" , "9" ]); 155assertEquals ( index . page ( did ). stars . length , 1 ); 156} finally { 157await server . shutdown (); 158db . close (); 159} 160});