···6969 print "waiting for collection-index discovery pass..."
7070 mut discovered = false
7171 for i in 1..60 {
7272- let stats = (try { (http get $"($url)/stats?accurate=true").counts } catch { {} })
7272+ let stats = (try { (http get $"($url)/stats").counts } catch { {} })
7373 let repos = ($stats | get --optional repos | default 0 | into int)
7474 print $"[($i)/60] repos: ($repos)"
7575 if $repos >= ($known_dids | length) {
+1-1
tests/common.nu
···174174export def wait-for-backfill [url: string] {
175175 print "waiting for backfill to complete..."
176176 for i in 1..120 {
177177- let stats = (http get $"($url)/stats?accurate=true").counts
177177+ let stats = (http get $"($url)/stats").counts
178178 let pending = ($stats.pending | into int)
179179 let records = ($stats.records | into int)
180180 let repos = ($stats.repos | into int)
+170
tests/count_tracking.nu
···11+#!/usr/bin/env nu
22+use common.nu *
33+44+def record-data [text: string] {
55+ let timestamp = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
66+ {
77+ "$type": "app.bsky.feed.post",
88+ text: $text,
99+ createdAt: $timestamp
1010+ }
1111+}
1212+1313+def api-count [url: string, did: string, collection: string] {
1414+ (http get $"($url)/xrpc/systems.gaze.hydrant.countRecords?identifier=($did)&collection=($collection)").count | into int
1515+}
1616+1717+def debug-count [debug_url: string, did: string, collection: string] {
1818+ (http get $"($debug_url)/debug/count?did=($did)&collection=($collection)").count | into int
1919+}
2020+2121+def stats-records [url: string] {
2222+ (http get $"($url)/stats").counts.records | into int
2323+}
2424+2525+def count-state [url: string, debug_url: string, did: string, collection: string] {
2626+ {
2727+ api: (api-count $url $did $collection),
2828+ debug: (debug-count $debug_url $did $collection),
2929+ records: (stats-records $url)
3030+ }
3131+}
3232+3333+def assert-count-state [label: string, state: record, expected_collection: int, expected_records: int] {
3434+ print $"($label): api=($state.api), debug=($state.debug), stats.records=($state.records)"
3535+3636+ if $state.api != $state.debug {
3737+ error make {msg: $"($label): countRecords mismatch. api=($state.api) debug=($state.debug)"}
3838+ }
3939+4040+ if $state.api != $expected_collection {
4141+ error make {msg: $"($label): expected collection count ($expected_collection), got ($state.api)"}
4242+ }
4343+4444+ if $state.records != $expected_records {
4545+ error make {msg: $"($label): expected stats.records ($expected_records), got ($state.records)"}
4646+ }
4747+}
4848+4949+def set-firehose [url: string, enabled: bool] {
5050+ http patch -t application/json $"($url)/ingestion" { firehose: $enabled } | ignore
5151+ let status = (http get $"($url)/ingestion")
5252+ if $status.firehose != $enabled {
5353+ error make {msg: $"expected firehose=($enabled), got ($status.firehose)"}
5454+ }
5555+}
5656+5757+def force-resync [url: string, did: string] {
5858+ let resp = (http post -f -e -t application/json $"($url)/repos/resync" [{ did: $did }])
5959+ if $resp.status != 200 {
6060+ error make {msg: $"repos/resync failed: ($resp.status) body=($resp.body)"}
6161+ }
6262+}
6363+6464+def main [] {
6565+ let env_vars = load-env-file
6666+ let did = ($env_vars | get --optional TEST_REPO)
6767+ let password = ($env_vars | get --optional TEST_PASSWORD)
6868+6969+ if ($did | is-empty) or ($password | is-empty) {
7070+ print "SKIP: TEST_REPO and TEST_PASSWORD not set in .env"
7171+ exit 0
7272+ }
7373+7474+ let pds_url = resolve-pds $did
7575+ let collection = "app.bsky.feed.post"
7676+ let port = resolve-test-port 3008
7777+ let debug_port = resolve-test-debug-port ($port + 1)
7878+ let url = $"http://localhost:($port)"
7979+ let debug_url = $"http://localhost:($debug_port)"
8080+ let db_path = (mktemp -d -t hydrant_count_tracking.XXXXXX)
8181+8282+ print $"testing count tracking for ($did)..."
8383+ print $"database path: ($db_path)"
8484+8585+ let session = authenticate $pds_url $did $password
8686+ let jwt = $session.accessJwt
8787+8888+ let seed = create-record $pds_url $jwt $did $collection (record-data "hydrant count seed")
8989+ let seed_rkey = ($seed.uri | split row "/" | last)
9090+ mut stale_rkey = ""
9191+ mut instance = null
9292+ mut success = false
9393+9494+ try {
9595+ let binary = build-hydrant
9696+ let relay = "wss://relay.fire.hose.cam"
9797+ $instance = (with-env { HYDRANT_RELAY_HOSTS: $relay } {
9898+ start-hydrant $binary $db_path $port
9999+ })
100100+101101+ if not (wait-for-api $url) {
102102+ error make {msg: "api failed to start"}
103103+ }
104104+105105+ print $"adding repo ($did) to tracking..."
106106+ http put -t application/json $"($url)/repos" [{ did: $did }]
107107+108108+ if not (wait-for-backfill $url) {
109109+ error make {msg: "initial backfill failed"}
110110+ }
111111+112112+ let baseline = (count-state $url $debug_url $did $collection)
113113+ if $baseline.api < 1 {
114114+ error make {msg: $"expected at least one seeded record, got ($baseline.api)"}
115115+ }
116116+ assert-count-state "baseline" $baseline $baseline.api $baseline.records
117117+118118+ print "pausing firehose..."
119119+ set-firehose $url false
120120+121121+ let stale = create-record $pds_url $jwt $did $collection (record-data "hydrant count stale create")
122122+ $stale_rkey = ($stale.uri | split row "/" | last)
123123+ sleep 2sec
124124+125125+ let stale_before_resync = (count-state $url $debug_url $did $collection)
126126+ assert-count-state "stale before resync" $stale_before_resync $baseline.api $baseline.records
127127+128128+ print "forcing resync after stale create..."
129129+ force-resync $url $did
130130+ if not (wait-for-backfill $url) {
131131+ error make {msg: "resync after create failed"}
132132+ }
133133+134134+ let after_create = (count-state $url $debug_url $did $collection)
135135+ assert-count-state "after create resync" $after_create ($baseline.api + 1) ($baseline.records + 1)
136136+137137+ delete-record $pds_url $jwt $did $collection $stale_rkey
138138+ sleep 2sec
139139+140140+ let stale_before_delete_resync = (count-state $url $debug_url $did $collection)
141141+ assert-count-state "stale before delete resync" $stale_before_delete_resync ($baseline.api + 1) ($baseline.records + 1)
142142+143143+ print "forcing resync after stale delete..."
144144+ force-resync $url $did
145145+ if not (wait-for-backfill $url) {
146146+ error make {msg: "resync after delete failed"}
147147+ }
148148+149149+ let after_delete = (count-state $url $debug_url $did $collection)
150150+ assert-count-state "after delete resync" $after_delete $baseline.api $baseline.records
151151+152152+ $success = true
153153+ } catch { |e|
154154+ print $"test failed: ($e.msg)"
155155+ }
156156+157157+ if ($instance | describe) != "nothing" {
158158+ try { set-firehose $url true }
159159+ try { kill --force $instance.pid }
160160+ }
161161+162162+ if ($stale_rkey | is-not-empty) {
163163+ try { delete-record $pds_url $jwt $did $collection $stale_rkey }
164164+ }
165165+ try { delete-record $pds_url $jwt $did $collection $seed_rkey }
166166+167167+ if not $success {
168168+ exit 1
169169+ }
170170+}
+2-1
tests/run_all.nu
···4848 # discover all test scripts, excluding infrastructure files
4949 mut excluded = ["common", "mock_relay", "mock_pds", "run_all"]
5050 if $skip_creds {
5151- $excluded = ($excluded | append ["authenticated_stream", "repo_sync_integrity"])
5151+ $excluded = ($excluded | append ["authenticated_stream", "count_tracking", "repo_sync_integrity"])
5252 }
5353 let discovered = (
5454 ls tests/*.nu
···78787979 let groups = {
8080 "authenticated_stream": "event_dependent",
8181+ "count_tracking": "event_dependent",
8182 "signal_filter": "event_dependent",
8283 }
8384 let grouped = $assigned | group-by {|t| $groups | get -o $t.name | default $t.name}
+1-1
tests/stream.nu
···67676868 sleep 2sec
69697070- let stats = (http get $"($url)/stats?accurate=true").counts
7070+ let stats = (http get $"($url)/stats").counts
7171 let events_count = ($stats.events | into int)
7272 print $"total events in db: ($events_count)"
7373
+1-1
tests/throttling.nu
···62626363 # retry check for 30s
6464 for i in 1..30 {
6565- let stats = (http get $"($url)/stats?accurate=true").counts
6565+ let stats = (http get $"($url)/stats").counts
6666 let pending = ($stats.pending | into int)
67676868 # we expect 5 repos from the mock, but max pending is 2.
+1-1
tests/verify_crawler.nu
···67676868 # retry check for 30s
6969 for i in 1..30 {
7070- let stats = (http get $"($url)/stats?accurate=true").counts
7070+ let stats = (http get $"($url)/stats").counts
7171 let pending = ($stats.pending | into int)
7272 let repos = ($stats.repos | default 0 | into int)
7373