A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 4.7 kB View raw
1import { describe, expect, it } from 'vitest'; 2import { 3 getClientMode, 4 clientMetadataUrl, 5 normalizeHandle, 6 isValidAccountInput, 7 isValidHandleOrDid, 8 redirectUriForLocation, 9 OAUTH_REDIRECT_PATHS, 10 OAUTH_SCOPE, 11} from './config'; 12 13describe( 'getClientMode', () => { 14 it( 'treats loopback hosts as dev (loopback) clients', () => { 15 expect( getClientMode( 'localhost' ) ).toBe( 'loopback' ); 16 expect( getClientMode( '127.0.0.1' ) ).toBe( 'loopback' ); 17 expect( getClientMode( '[::1]' ) ).toBe( 'loopback' ); 18 } ); 19 20 it( 'treats real hosts as hosted clients', () => { 21 expect( getClientMode( 'skypress.blog' ) ).toBe( 'hosted' ); 22 } ); 23} ); 24 25describe( 'clientMetadataUrl', () => { 26 it( 'points at /client-metadata.json on the app origin', () => { 27 expect( clientMetadataUrl( 'https://skypress.blog' ) ).toBe( 28 'https://skypress.blog/client-metadata.json' 29 ); 30 } ); 31} ); 32 33describe( 'OAUTH_REDIRECT_PATHS', () => { 34 it( 'lists every route that mounts the OAuth island, /editor/ first (the default)', () => { 35 // Both editor surfaces must be registered redirect targets, or atproto rejects 36 // the round-trip; /editor/ stays first so it is the safe fallback for any other 37 // page that ever triggers sign-in. 38 expect( OAUTH_REDIRECT_PATHS ).toEqual( [ '/editor/', '/write/' ] ); 39 } ); 40} ); 41 42describe( 'redirectUriForLocation', () => { 43 it( 'returns the originating page so the OAuth round-trip comes back to it', () => { 44 // The bug: signing in from /write returned the writer to /editor (the only 45 // registered redirect), orphaning the writing-first draft + publish intent. 46 expect( redirectUriForLocation( 'https://skypress.blog', '/write/' ) ).toBe( 47 'https://skypress.blog/write/' 48 ); 49 expect( redirectUriForLocation( 'https://skypress.blog', '/editor/' ) ).toBe( 50 'https://skypress.blog/editor/' 51 ); 52 } ); 53 54 it( 'normalises a missing trailing slash to the registered (slashed) path', () => { 55 expect( redirectUriForLocation( 'https://skypress.blog', '/write' ) ).toBe( 56 'https://skypress.blog/write/' 57 ); 58 } ); 59 60 it( 'falls back to /editor/ for any non-editor path', () => { 61 expect( redirectUriForLocation( 'https://skypress.blog', '/' ) ).toBe( 62 'https://skypress.blog/editor/' 63 ); 64 expect( redirectUriForLocation( 'https://skypress.blog', '/dashboard/' ) ).toBe( 65 'https://skypress.blog/editor/' 66 ); 67 } ); 68 69 it( 'strips a trailing slash off the origin before joining', () => { 70 expect( redirectUriForLocation( 'https://skypress.blog/', '/write/' ) ).toBe( 71 'https://skypress.blog/write/' 72 ); 73 } ); 74} ); 75 76describe( 'normalizeHandle', () => { 77 it( 'strips a leading @, trims, and lowercases', () => { 78 expect( normalizeHandle( ' @Alice.BSKY.social ' ) ).toBe( 'alice.bsky.social' ); 79 expect( normalizeHandle( 'bob.com' ) ).toBe( 'bob.com' ); 80 } ); 81} ); 82 83describe( 'isValidAccountInput', () => { 84 it( 'accepts handles, DIDs and PDS URLs', () => { 85 expect( isValidAccountInput( 'alice.bsky.social' ) ).toBe( true ); 86 expect( isValidAccountInput( '@alice.bsky.social' ) ).toBe( true ); 87 expect( isValidAccountInput( 'did:plc:abc123' ) ).toBe( true ); 88 expect( isValidAccountInput( 'https://pds.example.com' ) ).toBe( true ); 89 } ); 90 91 it( 'rejects empty input and bare words without a domain', () => { 92 expect( isValidAccountInput( '' ) ).toBe( false ); 93 expect( isValidAccountInput( ' ' ) ).toBe( false ); 94 expect( isValidAccountInput( 'notahandle' ) ).toBe( false ); 95 } ); 96} ); 97 98describe( 'isValidHandleOrDid', () => { 99 it( 'accepts handles and DIDs', () => { 100 expect( isValidHandleOrDid( 'alice.bsky.social' ) ).toBe( true ); 101 expect( isValidHandleOrDid( '@alice.bsky.social' ) ).toBe( true ); 102 expect( isValidHandleOrDid( 'did:plc:abc123' ) ).toBe( true ); 103 expect( isValidHandleOrDid( 'did:web:example.com' ) ).toBe( true ); 104 } ); 105 106 it( 'rejects URLs and anything carrying a path, port, query or scheme', () => { 107 // These would otherwise be used directly as a resolver fetch host (SSRF/proxy). 108 expect( isValidHandleOrDid( 'https://pds.example.com' ) ).toBe( false ); 109 expect( isValidHandleOrDid( 'evil.com/.well-known/atproto-did' ) ).toBe( false ); 110 expect( isValidHandleOrDid( 'evil.com?x=y' ) ).toBe( false ); 111 expect( isValidHandleOrDid( 'evil.com:8080' ) ).toBe( false ); 112 expect( isValidHandleOrDid( 'user@evil.com' ) ).toBe( false ); 113 } ); 114 115 it( 'rejects empty input and bare words without a domain', () => { 116 expect( isValidHandleOrDid( '' ) ).toBe( false ); 117 expect( isValidHandleOrDid( ' ' ) ).toBe( false ); 118 expect( isValidHandleOrDid( 'notahandle' ) ).toBe( false ); 119 } ); 120} ); 121 122describe( 'OAUTH_SCOPE', () => { 123 it( 'requests atproto + transitional generic access for SP1', () => { 124 expect( OAUTH_SCOPE ).toBe( 'atproto transition:generic' ); 125 } ); 126} );