perlsky is a Perl 5 implementation of an AT Protocol Personal Data Server.
13
fork

Configure Feed

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

Split discovery coverage from external surface suite

alice 8522d5a5 8b1979f2

+94 -30
+4 -3
docs/TEST_AUDIT.md
··· 116 116 Current suite counts by bucket: 117 117 118 118 - `direct reference differential`: `5` 119 - - `audited local regression`: `34` 119 + - `audited local regression`: `35` 120 120 - `local correctness/infrastructure`: `13` 121 121 122 122 | Test file | Bucket | Current note | ··· 133 133 | `t/crawlers.t` | audited local regression | outbound crawl notification semantics | 134 134 | `t/crypto-interop.t` | direct reference differential | pinned upstream crypto fixture coverage | 135 135 | `t/delete-account.t` | audited local regression | reference-style account deletion flow using DID, password, and action token without a live bearer session | 136 + | `t/discovery-surfaces.t` | audited local regression | isolated local discovery-surface coverage for endpoint catalog wiring, lexicon lookup, handle availability, and `listReposByCollection` | 136 137 | `t/email-confirmation.t` | audited local regression | intentionally testing-friendly email flow plus strict missing-email and invalid-email validation semantics | 137 138 | `t/email-update-helper.t` | audited local regression | shared email-update helper normalization, token revocation, and duplicate-email error semantics | 138 139 | `t/event-stream.t` | audited local regression | wire-format, malformed frame, and event decoding coverage | 139 140 | `t/extended-api.t` | audited local regression | mixes reference-aligned repo/sync/moderation happy paths with still-local label fetch/query smoke and account/invite flows, but is cleaner after splitting reserved-handle and crawl-host checks out | 140 141 | `t/external-handle-update.t` | audited local regression | external-handle update semantics, including DID-resolution checks and empty-body success for external handle adoption | 141 - | `t/external-surface.t` | audited local regression | mixes reference-aligned repo/sync/blob/account-status behavior with local-only surfaces such as `resolveLexicon`, `checkHandleAvailability`, and `listReposByCollection`; intentionally broad, with order-insensitive label assertions rather than brittle ordering | 142 + | `t/external-surface.t` | audited local regression | focused external-surface coverage for repo/blob/account-status and label behavior after splitting the local discovery surfaces out | 142 143 | `t/firehose.t` | audited local regression | repo subscription lifecycle, cursor, and CAR behavior | 143 144 | `t/identity.t` | local correctness/infrastructure | lower-level handle and DID helper coverage, including DNS-over-well-known preference and malformed-handle rejection | 144 145 | `t/import-repo.t` | audited local regression | focused `importRepo` snapshot-restore and rollback behavior, now cleaner after splitting the disabled-import policy gate into its own suite | ··· 181 182 - `t/extended-api.t` 182 183 Carries real conformance value for `applyWrites`, blob/sync flows, and moderation/label visibility, but it still mixes those with some local product behavior such as label fetch/query smoke and invite/account flows. 183 184 - `t/external-surface.t` 184 - Carries strong external-surface coverage for repo export, blob access, and account-status behavior, but also covers local-only surfaces such as `resolveLexicon`, `checkHandleAvailability`, and `listReposByCollection`. 185 + Carries strong external-surface coverage for repo export, blob access, account-status behavior, and label visibility. It is cleaner after moving discovery-specific checks into `t/discovery-surfaces.t`, but still remains broader than a single-endpoint conformance file. 185 186 - `t/import-repo.t` 186 187 Is close to a clean conformance suite, but still includes the local `accepting_imports` gate in the same file. 187 188 - `t/uncovered-endpoints.t`
+90
t/discovery-surfaces.t
··· 1 + use v5.34; 2 + use warnings; 3 + 4 + use Config (); 5 + use File::Spec; 6 + use File::Temp qw(tempdir); 7 + use FindBin qw($Bin); 8 + use Test::More; 9 + 10 + BEGIN { 11 + require lib; 12 + my $root = File::Spec->rel2abs(File::Spec->catdir($Bin, '..')); 13 + lib->import( 14 + File::Spec->catdir($root, 'lib'), 15 + File::Spec->catdir($root, 'local', 'lib', 'perl5'), 16 + File::Spec->catdir($root, 'local', 'lib', 'perl5', $Config::Config{archname}), 17 + ); 18 + } 19 + 20 + use Mojo::URL; 21 + use Test::Mojo; 22 + use ATProto::PDS; 23 + 24 + my $root = File::Spec->rel2abs(File::Spec->catdir($Bin, '..')); 25 + my $tmp = tempdir(CLEANUP => 1); 26 + 27 + my $app = ATProto::PDS->new( 28 + project_root => $root, 29 + settings => { 30 + base_url => 'http://127.0.0.1:7755', 31 + service_handle_domain => 'example.test', 32 + service_did_method => 'did:web', 33 + jwt_secret => 'discovery-surfaces-secret', 34 + admin_password => 'admin-secret', 35 + data_dir => File::Spec->catdir($tmp, 'data'), 36 + db_path => File::Spec->catfile($tmp, 'perlsky.sqlite'), 37 + }, 38 + ); 39 + 40 + my $t = Test::Mojo->new($app); 41 + 42 + for my $endpoint (@{ $app->endpoint_catalog }) { 43 + ok($app->api_registry->handler_for($endpoint->{id}), "$endpoint->{id} has a handler"); 44 + } 45 + 46 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.lexicon.resolveLexicon')->query( 47 + nsid => 'com.atproto.server.createSession', 48 + ))->status_is(200) 49 + ->json_is('/schema/id' => 'com.atproto.server.createSession') 50 + ->json_has('/cid') 51 + ->json_has('/uri'); 52 + 53 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.temp.checkHandleAvailability')->query( 54 + handle => 'alice.example.test', 55 + ))->status_is(200) 56 + ->json_is('/handle' => 'alice.example.test') 57 + ->json_has('/result'); 58 + 59 + $t->post_ok('/xrpc/com.atproto.server.createAccount' => json => { 60 + handle => 'alice.example.test', 61 + email => 'alice@example.test', 62 + password => 'hunter22', 63 + })->status_is(200); 64 + 65 + my $session = $t->tx->res->json; 66 + my $did = $session->{did}; 67 + my $access = $session->{accessJwt}; 68 + 69 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.temp.checkHandleAvailability')->query( 70 + handle => 'alice.example.test', 71 + ))->status_is(200) 72 + ->json_has('/result/suggestions/0/handle'); 73 + 74 + $t->post_ok('/xrpc/com.atproto.repo.createRecord' => { Authorization => "Bearer $access" } => json => { 75 + repo => $did, 76 + collection => 'app.bsky.feed.post', 77 + rkey => 'hello-world', 78 + record => { 79 + '$type' => 'app.bsky.feed.post', 80 + text => 'hello surface', 81 + createdAt => '2026-03-10T00:00:00Z', 82 + }, 83 + })->status_is(200); 84 + 85 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.listReposByCollection')->query( 86 + collection => 'app.bsky.feed.post', 87 + ))->status_is(200) 88 + ->json_is('/repos/0/did' => $did); 89 + 90 + done_testing;
-27
t/external-surface.t
··· 42 42 my $t = Test::Mojo->new($app); 43 43 my $admin_auth = 'Basic YWRtaW46YWRtaW4tc2VjcmV0'; 44 44 45 - for my $endpoint (@{ $app->endpoint_catalog }) { 46 - ok($app->api_registry->handler_for($endpoint->{id}), "$endpoint->{id} has a handler"); 47 - } 48 - 49 - $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.lexicon.resolveLexicon')->query( 50 - nsid => 'com.atproto.server.createSession', 51 - ))->status_is(200) 52 - ->json_is('/schema/id' => 'com.atproto.server.createSession') 53 - ->json_has('/cid') 54 - ->json_has('/uri'); 55 - 56 - $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.temp.checkHandleAvailability')->query( 57 - handle => 'alice.example.test', 58 - ))->status_is(200) 59 - ->json_is('/handle' => 'alice.example.test') 60 - ->json_has('/result'); 61 - 62 45 $t->post_ok('/xrpc/com.atproto.server.createAccount' => json => { 63 46 handle => 'alice.example.test', 64 47 email => 'alice@example.test', ··· 69 52 my $did = $session->{did}; 70 53 my $access = $session->{accessJwt}; 71 54 72 - $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.temp.checkHandleAvailability')->query( 73 - handle => 'alice.example.test', 74 - ))->status_is(200) 75 - ->json_has('/result/suggestions/0/handle'); 76 - 77 55 $t->post_ok('/xrpc/com.atproto.repo.createRecord' => { Authorization => "Bearer $access" } => json => { 78 56 repo => $did, 79 57 collection => 'app.bsky.feed.post', ··· 88 66 my $record = $t->tx->res->json; 89 67 my $record_uri = $record->{uri}; 90 68 my $record_cid = $record->{cid}; 91 - 92 - $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.listReposByCollection')->query( 93 - collection => 'app.bsky.feed.post', 94 - ))->status_is(200) 95 - ->json_is('/repos/0/did' => $did); 96 69 97 70 $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.getLatestCommit')->query( 98 71 did => $did,