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 account self-service flows from extended API suite

alice b532fb54 7e1ce7f4

+111 -59
+5 -4
docs/TEST_AUDIT.md
··· 13 13 The current baseline for saying "the audited suite is green" is: 14 14 15 15 - `prove -lr t` 16 - - latest full green result in the realigned Meridian worktree: `Files=56, Tests=3049` 16 + - latest full green result in the realigned Meridian worktree: `Files=57, Tests=3051` 17 17 - `prove -lv t/server-auth.t` 18 18 - `perl -c script/differential-validate` 19 19 - `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t` ··· 116 116 Current suite counts by bucket: 117 117 118 118 - `direct reference differential`: `5` 119 - - `audited local regression`: `38` 119 + - `audited local regression`: `39` 120 120 - `local correctness/infrastructure`: `13` 121 121 122 122 | Test file | Bucket | Current note | ··· 126 126 | `t/app-routes.t` | local correctness/infrastructure | app route exposure and startup wiring smoke | 127 127 | `t/app.t` | audited local regression | application bootstrap plus malformed-handle rejection and startup hardening | 128 128 | `t/admin-account-surfaces.t` | audited local regression | isolated admin account-maintenance coverage for handle/email/password/signing-key/send-email/subject-status behaviors | 129 + | `t/account-self-service-surfaces.t` | audited local regression | isolated self-service account maintenance coverage for admin account lookup, identity refresh/update, and email update/confirmation flows | 129 130 | `t/account-migration-auth.t` | audited local regression | explicit-`did` account creation requires authenticated migration service-auth and preserves remote DID-doc state while starting deactivated | 130 131 | `t/auth-jwt.t` | local correctness/infrastructure | JWT signing and validation behavior | 131 132 | `t/blob-sync-surfaces.t` | audited local regression | isolated blob upload and sync happy-path coverage for `uploadBlob`, `listBlobs`, `getBlob`, `getLatestCommit`, and `getBlocks` | ··· 139 140 | `t/email-confirmation.t` | audited local regression | intentionally testing-friendly email flow plus strict missing-email and invalid-email validation semantics | 140 141 | `t/email-update-helper.t` | audited local regression | shared email-update helper normalization, token revocation, and duplicate-email error semantics | 141 142 | `t/event-stream.t` | audited local regression | wire-format, malformed frame, and event decoding coverage | 142 - | `t/extended-api.t` | audited local regression | focused mixed coverage for invite issuance, `applyWrites`, identity refresh/update, and email flows after the label and blob/sync happy paths were split out | 143 + | `t/extended-api.t` | audited local regression | focused mixed coverage for self-service invite issuance and `applyWrites` after the identity/email, label, and blob/sync happy paths were split out | 143 144 | `t/external-handle-update.t` | audited local regression | external-handle update semantics, including DID-resolution checks and empty-body success for external handle adoption | 144 145 | `t/external-surface.t` | audited local regression | focused external-surface coverage for repo/blob/account-status and missing-blob behavior after splitting discovery and label RPC checks into dedicated suites | 145 146 | `t/firehose.t` | audited local regression | repo subscription lifecycle, cursor, and CAR behavior | ··· 183 184 The broadest suites are green and audited, but they still mix several categories of behavior inside the same file: 184 185 185 186 - `t/extended-api.t` 186 - Carries real conformance value for `applyWrites` and account/email identity lifecycle behavior, but it still mixes those with local product behavior such as self-service invite flows. 187 + Carries real conformance value for `applyWrites`, but it still mixes those with local product behavior such as self-service invite flows. 187 188 - `t/external-surface.t` 188 189 Carries strong external-surface coverage for repo export, blob access, account-status behavior, and missing-blob listing. It is cleaner after moving discovery and label-RPC checks into dedicated suites, but still remains broader than a single-endpoint conformance file. 189 190 - `t/uncovered-endpoints.t`
+106
t/account-self-service-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 Test::Mojo; 21 + use ATProto::PDS; 22 + 23 + my $root = File::Spec->rel2abs(File::Spec->catdir($Bin, '..')); 24 + my $tmp = tempdir(CLEANUP => 1); 25 + 26 + my $app = ATProto::PDS->new( 27 + project_root => $root, 28 + settings => { 29 + base_url => 'http://127.0.0.1:7755', 30 + service_handle_domain => 'example.test', 31 + service_did_method => 'did:web', 32 + jwt_secret => 'account-self-service-secret', 33 + admin_password => 'admin-secret', 34 + testing_auto_confirm_email => 1, 35 + data_dir => $tmp, 36 + db_path => File::Spec->catfile($tmp, 'perlsky.sqlite'), 37 + }, 38 + ); 39 + 40 + my $t = Test::Mojo->new($app); 41 + my $admin_auth = 'Basic YWRtaW46YWRtaW4tc2VjcmV0'; 42 + 43 + $t->post_ok('/xrpc/com.atproto.server.createAccount' => json => { 44 + handle => 'alice.example.test', 45 + email => 'alice@example.test', 46 + password => 'hunter22', 47 + })->status_is(200); 48 + 49 + my $created = $t->tx->res->json; 50 + my $access = $created->{accessJwt}; 51 + my $did = $created->{did}; 52 + 53 + $t->get_ok('/xrpc/com.atproto.admin.getAccountInfo' => { 54 + Authorization => $admin_auth, 55 + } => form => { 56 + did => $did, 57 + })->status_is(200) 58 + ->json_is('/did', $did) 59 + ->json_is('/handle', 'alice.example.test'); 60 + 61 + $t->post_ok('/xrpc/com.atproto.identity.updateHandle' => { 62 + Authorization => "Bearer $access", 63 + } => json => { 64 + handle => 'alice-renamed.example.test', 65 + })->status_is(200); 66 + 67 + $t->post_ok('/xrpc/com.atproto.identity.refreshIdentity' => json => { 68 + identifier => 'alice-renamed.example.test', 69 + })->status_is(200) 70 + ->json_is('/did', $did) 71 + ->json_is('/handle', 'alice-renamed.example.test'); 72 + 73 + $t->post_ok('/xrpc/com.atproto.server.requestEmailUpdate' => { 74 + Authorization => "Bearer $access", 75 + } => json => {})->status_is(200); 76 + ok($t->tx->res->json->{tokenRequired}, 'confirmed email requires update token'); 77 + 78 + my $email_update = $app->store->latest_action_token( 79 + did => $did, 80 + purpose => 'email_update', 81 + ); 82 + 83 + $t->post_ok('/xrpc/com.atproto.server.updateEmail' => { 84 + Authorization => "Bearer $access", 85 + } => json => { 86 + email => 'alice+new@example.test', 87 + token => $email_update->{token}, 88 + })->status_is(200); 89 + 90 + $t->post_ok('/xrpc/com.atproto.server.requestEmailConfirmation' => { 91 + Authorization => "Bearer $access", 92 + } => json => {})->status_is(200); 93 + 94 + my $email_confirm = $app->store->latest_action_token( 95 + did => $did, 96 + purpose => 'email_confirm', 97 + ); 98 + 99 + $t->post_ok('/xrpc/com.atproto.server.confirmEmail' => { 100 + Authorization => "Bearer $access", 101 + } => json => { 102 + email => 'alice+new@example.test', 103 + token => $email_confirm->{token}, 104 + })->status_is(200); 105 + 106 + done_testing;
-55
t/extended-api.t
··· 40 40 ); 41 41 42 42 my $t = Test::Mojo->new($app); 43 - my $admin_auth = 'Basic YWRtaW46YWRtaW4tc2VjcmV0'; 44 - 45 43 $t->post_ok('/xrpc/com.atproto.server.createAccount' => json => { 46 44 handle => 'alice.example.test', 47 45 email => 'alice@example.test', ··· 160 158 Authorization => "Bearer $access", 161 159 })->status_is(200) 162 160 ->json_is('/codes/0/code', $invite_code); 163 - 164 - $t->get_ok('/xrpc/com.atproto.admin.getAccountInfo' => { 165 - Authorization => $admin_auth, 166 - } => form => { 167 - did => $did, 168 - })->status_is(200) 169 - ->json_is('/did', $did) 170 - ->json_is('/handle', 'alice.example.test'); 171 - 172 - $t->post_ok('/xrpc/com.atproto.identity.updateHandle' => { 173 - Authorization => "Bearer $access", 174 - } => json => { 175 - handle => 'alice-renamed.example.test', 176 - })->status_is(200); 177 - 178 - $t->post_ok('/xrpc/com.atproto.identity.refreshIdentity' => json => { 179 - identifier => 'alice-renamed.example.test', 180 - })->status_is(200) 181 - ->json_is('/did', $did) 182 - ->json_is('/handle', 'alice-renamed.example.test'); 183 - 184 - $t->post_ok('/xrpc/com.atproto.server.requestEmailUpdate' => { 185 - Authorization => "Bearer $access", 186 - } => json => {})->status_is(200); 187 - ok($t->tx->res->json->{tokenRequired}, 'confirmed email requires update token'); 188 - 189 - my $email_update = $app->store->latest_action_token( 190 - did => $did, 191 - purpose => 'email_update', 192 - ); 193 - 194 - $t->post_ok('/xrpc/com.atproto.server.updateEmail' => { 195 - Authorization => "Bearer $access", 196 - } => json => { 197 - email => 'alice+new@example.test', 198 - token => $email_update->{token}, 199 - })->status_is(200); 200 - 201 - $t->post_ok('/xrpc/com.atproto.server.requestEmailConfirmation' => { 202 - Authorization => "Bearer $access", 203 - } => json => {})->status_is(200); 204 - 205 - my $email_confirm = $app->store->latest_action_token( 206 - did => $did, 207 - purpose => 'email_confirm', 208 - ); 209 - 210 - $t->post_ok('/xrpc/com.atproto.server.confirmEmail' => { 211 - Authorization => "Bearer $access", 212 - } => json => { 213 - email => 'alice+new@example.test', 214 - token => $email_confirm->{token}, 215 - })->status_is(200); 216 161 217 162 done_testing;