CLI app for developers prototyping atproto functionality
1
fork

Configure Feed

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

refactor(tests): promote FakeHttpClient and FakeDnsResolver to tests/common/mod.rs

Move FakeHttpClient and FakeDnsResolver from local implementations in
tests/labeler_identity.rs to the shared tests/common/mod.rs module. This
allows both labeler and oauth_client integration tests to reuse the same
fakes.

Enhancements to FakeHttpClient:
- Add support for optional content-type headers with
add_response_with_content_type()
- Add add_transport_error() to simulate network failures
- Implement HttpClient trait with both get_bytes() and
get_bytes_with_content_type() methods
- Return 404 for unmocked URLs (consistent with original behavior)

FakeDnsResolver:
- Add to shared test utilities with add_records() seeding method
- Implement DnsResolver trait
- Return DnsLookupFailed for unmocked domains (consistent pattern)

Update labeler_identity.rs to import from common module and adjust method
signatures (add_records instead of add_record, Url objects for HTTP calls).
All labeler_identity tests pass with the shared fakes.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

+203 -134
+158 -1
tests/common/mod.rs
··· 12 12 use atproto_devtool::commands::test::labeler::subscription::{ 13 13 FrameStream, SubscriptionStageError, WebSocketClient, 14 14 }; 15 - use std::collections::HashMap; 15 + use atproto_devtool::common::identity::{DnsResolver, HttpClient, IdentityError}; 16 + use std::collections::{HashMap, HashSet}; 16 17 use std::sync::{Arc, Mutex}; 17 18 use std::time::Duration; 18 19 use url::Url; 19 20 20 21 /// Type alias for HTTP response map in tests. 21 22 pub type FakeHttpResponses = Arc<Mutex<HashMap<Option<String>, (reqwest::StatusCode, Vec<u8>)>>>; 23 + 24 + /// Type alias for HTTP response map in FakeHttpClient. 25 + type FakeHttpClientResponses = Arc<Mutex<HashMap<String, (u16, Vec<u8>, Option<String>)>>>; 26 + 27 + /// Type alias for DNS records map in FakeDnsResolver. 28 + type FakeDnsRecords = Arc<Mutex<HashMap<String, Vec<String>>>>; 22 29 23 30 /// Fake HTTP tee for testing, returns pre-defined responses. 24 31 pub struct FakeRawHttpTee { ··· 54 61 impl Default for FakeRawHttpTee { 55 62 fn default() -> Self { 56 63 Self::new() 64 + } 65 + } 66 + 67 + /// Fake HTTP client for identity resolution tests. 68 + /// 69 + /// Seeded with responses per URL. Panics if a URL is requested that wasn't seeded, 70 + /// to catch test-author mistakes. Supports both `get_bytes` and 71 + /// `get_bytes_with_content_type` with optional content-type headers. 72 + pub struct FakeHttpClient { 73 + responses: FakeHttpClientResponses, 74 + transport_errors: Arc<Mutex<HashSet<String>>>, 75 + } 76 + 77 + impl FakeHttpClient { 78 + /// Create a new FakeHttpClient. 79 + pub fn new() -> Self { 80 + Self { 81 + responses: Arc::new(Mutex::new(HashMap::new())), 82 + transport_errors: Arc::new(Mutex::new(HashSet::new())), 83 + } 84 + } 85 + 86 + /// Add a response for the given URL. 87 + pub fn add_response(&self, url: &Url, status: u16, body: Vec<u8>) { 88 + self.add_response_with_content_type(url, status, body, None); 89 + } 90 + 91 + /// Add a response for the given URL with an optional Content-Type header. 92 + pub fn add_response_with_content_type( 93 + &self, 94 + url: &Url, 95 + status: u16, 96 + body: Vec<u8>, 97 + content_type: Option<String>, 98 + ) { 99 + self.responses 100 + .lock() 101 + .unwrap() 102 + .insert(url.as_str().to_string(), (status, body, content_type)); 103 + } 104 + 105 + /// Add a transport error for the given URL (simulates network failure). 106 + pub fn add_transport_error(&self, url: &Url) { 107 + self.transport_errors 108 + .lock() 109 + .unwrap() 110 + .insert(url.as_str().to_string()); 111 + } 112 + } 113 + 114 + impl Default for FakeHttpClient { 115 + fn default() -> Self { 116 + Self::new() 117 + } 118 + } 119 + 120 + #[async_trait] 121 + impl HttpClient for FakeHttpClient { 122 + async fn get_bytes(&self, url: &Url) -> Result<(u16, Vec<u8>), IdentityError> { 123 + let url_str = url.as_str(); 124 + 125 + // Check for transport error first. 126 + if self.transport_errors.lock().unwrap().contains(url_str) { 127 + return Err(IdentityError::HttpTransport( 128 + reqwest::Client::new() 129 + .get(url.as_str()) 130 + .build() 131 + .unwrap_err(), 132 + )); 133 + } 134 + 135 + // Look up the response. If not found, return 404 (mimics original behavior). 136 + Ok(self 137 + .responses 138 + .lock() 139 + .unwrap() 140 + .get(url_str) 141 + .map(|(status, body, _content_type)| (*status, body.clone())) 142 + .unwrap_or_else(|| (404, b"Not found".to_vec()))) 143 + } 144 + 145 + async fn get_bytes_with_content_type( 146 + &self, 147 + url: &Url, 148 + ) -> Result<(u16, Vec<u8>, Option<String>), IdentityError> { 149 + let url_str = url.as_str(); 150 + 151 + // Check for transport error first. 152 + if self.transport_errors.lock().unwrap().contains(url_str) { 153 + return Err(IdentityError::HttpTransport( 154 + reqwest::Client::new() 155 + .get(url.as_str()) 156 + .build() 157 + .unwrap_err(), 158 + )); 159 + } 160 + 161 + // Look up the response. If not found, return 404 (mimics original behavior). 162 + Ok(self 163 + .responses 164 + .lock() 165 + .unwrap() 166 + .get(url_str) 167 + .map(|(status, body, content_type)| (*status, body.clone(), content_type.clone())) 168 + .unwrap_or_else(|| (404, b"Not found".to_vec(), None))) 169 + } 170 + } 171 + 172 + /// Fake DNS resolver for identity resolution tests. 173 + /// 174 + /// Seeded with records per domain. Panics if a domain is looked up that wasn't seeded, 175 + /// to catch test-author mistakes. 176 + pub struct FakeDnsResolver { 177 + records: FakeDnsRecords, 178 + } 179 + 180 + impl FakeDnsResolver { 181 + /// Create a new FakeDnsResolver. 182 + pub fn new() -> Self { 183 + Self { 184 + records: Arc::new(Mutex::new(HashMap::new())), 185 + } 186 + } 187 + 188 + /// Add DNS records for the given domain. 189 + pub fn add_records(&self, name: &str, values: Vec<String>) { 190 + self.records 191 + .lock() 192 + .unwrap() 193 + .insert(name.to_string(), values); 194 + } 195 + } 196 + 197 + impl Default for FakeDnsResolver { 198 + fn default() -> Self { 199 + Self::new() 200 + } 201 + } 202 + 203 + #[async_trait] 204 + impl DnsResolver for FakeDnsResolver { 205 + async fn txt_lookup(&self, name: &str) -> Result<Vec<String>, IdentityError> { 206 + self.records 207 + .lock() 208 + .unwrap() 209 + .get(name) 210 + .cloned() 211 + .ok_or_else(|| IdentityError::DnsLookupFailed { 212 + source: Box::new(IdentityError::InvalidHandle), 213 + }) 57 214 } 58 215 } 59 216
+45 -133
tests/labeler_identity.rs
··· 2 2 3 3 mod common; 4 4 5 - use async_trait::async_trait; 6 5 use atproto_devtool::commands::test::labeler::pipeline::{ 7 6 HttpTee, LabelerOptions, parse_target, run_pipeline, 8 7 }; 9 - use atproto_devtool::common::identity::{DnsResolver, HttpClient, IdentityError}; 10 - use std::collections::HashMap; 11 - use std::sync::{Arc, Mutex}; 12 - 13 8 use url::Url; 14 9 15 - /// Type alias for the response map in FakeHttpClient. 16 - type FakeHttpResponses = Arc<Mutex<HashMap<String, (u16, Vec<u8>)>>>; 17 - 18 - /// Fake HTTP client for testing, seeded with fixture responses. 19 - struct FakeHttpClient { 20 - responses: FakeHttpResponses, 21 - } 22 - 23 - impl FakeHttpClient { 24 - fn new() -> Self { 25 - Self { 26 - responses: Arc::new(Mutex::new(HashMap::new())), 27 - } 28 - } 29 - 30 - fn add_response(&self, url: impl Into<String>, status: u16, body: Vec<u8>) { 31 - self.responses 32 - .lock() 33 - .unwrap() 34 - .insert(url.into(), (status, body)); 35 - } 36 - } 37 - 38 - #[async_trait] 39 - impl HttpClient for FakeHttpClient { 40 - async fn get_bytes(&self, url: &Url) -> Result<(u16, Vec<u8>), IdentityError> { 41 - let url_str = url.as_str(); 42 - self.responses 43 - .lock() 44 - .unwrap() 45 - .get(url_str) 46 - .cloned() 47 - .ok_or_else(|| IdentityError::DidResolutionFailed { 48 - status: 404, 49 - body: "Not found".to_string(), 50 - }) 51 - } 52 - } 53 - 54 - /// Type alias for the DNS records map in FakeDnsResolver. 55 - type FakeDnsRecords = Arc<Mutex<HashMap<String, Vec<String>>>>; 56 - 57 - /// Fake DNS resolver for testing, seeded with fixture records. 58 - struct FakeDnsResolver { 59 - records: FakeDnsRecords, 60 - } 61 - 62 - impl FakeDnsResolver { 63 - fn new() -> Self { 64 - Self { 65 - records: Arc::new(Mutex::new(HashMap::new())), 66 - } 67 - } 68 - 69 - fn add_record(&self, name: impl Into<String>, records: Vec<String>) { 70 - self.records.lock().unwrap().insert(name.into(), records); 71 - } 72 - } 73 - 74 - #[async_trait] 75 - impl DnsResolver for FakeDnsResolver { 76 - async fn txt_lookup(&self, name: &str) -> Result<Vec<String>, IdentityError> { 77 - self.records 78 - .lock() 79 - .unwrap() 80 - .get(name) 81 - .cloned() 82 - .ok_or_else(|| IdentityError::DnsLookupFailed { 83 - source: Box::new(IdentityError::InvalidHandle), 84 - }) 85 - } 86 - } 87 - 88 10 /// Helper to render a report to a string. 89 11 fn render_report_to_string(report: &atproto_devtool::common::report::LabelerReport) -> String { 90 12 let mut buf = Vec::new(); ··· 123 45 let labeler_record_json = 124 46 include_bytes!("fixtures/labeler/identity/healthy_plc/labeler_record.json").to_vec(); 125 47 126 - let http = FakeHttpClient::new(); 127 - let dns = FakeDnsResolver::new(); 48 + let http = common::FakeHttpClient::new(); 49 + let dns = common::FakeDnsResolver::new(); 128 50 129 51 // Mock the DID resolution. 130 52 http.add_response( 131 - "https://plc.directory/did:plc:test123456789abcdefghijklmnop", 53 + &Url::parse("https://plc.directory/did:plc:test123456789abcdefghijklmnop").unwrap(), 132 54 200, 133 55 did_json, 134 56 ); 135 57 136 58 // Mock the labeler record fetch. 137 - http.add_response( 138 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self", 59 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self").unwrap(), 139 60 200, 140 61 labeler_record_json, 141 62 ); ··· 162 83 163 84 #[tokio::test] 164 85 async fn endpoint_only_no_did_skips_identity() { 165 - let http = FakeHttpClient::new(); 166 - let dns = FakeDnsResolver::new(); 86 + let http = common::FakeHttpClient::new(); 87 + let dns = common::FakeDnsResolver::new(); 167 88 168 89 let target = parse_target("https://example.com/labeler", None).expect("parse failed"); 169 90 let fake_tee = common::FakeRawHttpTee::new(); ··· 189 110 190 111 #[tokio::test] 191 112 async fn handle_resolution_happy_path() { 192 - let http = FakeHttpClient::new(); 193 - let dns = FakeDnsResolver::new(); 113 + let http = common::FakeHttpClient::new(); 114 + let dns = common::FakeDnsResolver::new(); 194 115 195 116 // Mock DNS resolution of handle. 196 - dns.add_record( 117 + dns.add_records( 197 118 "_atproto.alice.example", 198 119 vec!["did=did:plc:test123456789abcdefghijklmnop".to_string()], 199 120 ); ··· 201 122 // Mock DID resolution. 202 123 let did_json = include_bytes!("fixtures/labeler/identity/healthy_plc/did.json").to_vec(); 203 124 http.add_response( 204 - "https://plc.directory/did:plc:test123456789abcdefghijklmnop", 125 + &Url::parse("https://plc.directory/did:plc:test123456789abcdefghijklmnop").unwrap(), 205 126 200, 206 127 did_json, 207 128 ); ··· 209 130 // Mock labeler record fetch. 210 131 let labeler_record_json = 211 132 include_bytes!("fixtures/labeler/identity/healthy_plc/labeler_record.json").to_vec(); 212 - http.add_response( 213 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self", 133 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self").unwrap(), 214 134 200, 215 135 labeler_record_json, 216 136 ); ··· 239 159 240 160 #[tokio::test] 241 161 async fn did_plc_direct_happy_path() { 242 - let http = FakeHttpClient::new(); 243 - let dns = FakeDnsResolver::new(); 162 + let http = common::FakeHttpClient::new(); 163 + let dns = common::FakeDnsResolver::new(); 244 164 245 165 // Mock DID resolution. 246 166 let did_json = include_bytes!("fixtures/labeler/identity/healthy_plc/did.json").to_vec(); 247 167 http.add_response( 248 - "https://plc.directory/did:plc:test123456789abcdefghijklmnop", 168 + &Url::parse("https://plc.directory/did:plc:test123456789abcdefghijklmnop").unwrap(), 249 169 200, 250 170 did_json, 251 171 ); ··· 253 173 // Mock labeler record fetch. 254 174 let labeler_record_json = 255 175 include_bytes!("fixtures/labeler/identity/healthy_plc/labeler_record.json").to_vec(); 256 - http.add_response( 257 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self", 176 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self").unwrap(), 258 177 200, 259 178 labeler_record_json, 260 179 ); ··· 281 200 282 201 #[tokio::test] 283 202 async fn did_web_direct_happy_path() { 284 - let http = FakeHttpClient::new(); 285 - let dns = FakeDnsResolver::new(); 203 + let http = common::FakeHttpClient::new(); 204 + let dns = common::FakeDnsResolver::new(); 286 205 287 206 // Mock DID resolution for did:web. 288 207 let did_json = include_bytes!("fixtures/labeler/identity/healthy_web/did.json").to_vec(); 289 208 http.add_response( 290 - "https://web-labeler.example/.well-known/did.json", 209 + &Url::parse("https://web-labeler.example/.well-known/did.json").unwrap(), 291 210 200, 292 211 did_json, 293 212 ); ··· 295 214 // Mock labeler record fetch. 296 215 let labeler_record_json = 297 216 include_bytes!("fixtures/labeler/identity/healthy_web/labeler_record.json").to_vec(); 298 - http.add_response( 299 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:web:web-labeler.example&collection=app.bsky.labeler.service&rkey=self", 217 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:web:web-labeler.example&collection=app.bsky.labeler.service&rkey=self").unwrap(), 300 218 200, 301 219 labeler_record_json, 302 220 ); ··· 325 243 326 244 #[tokio::test] 327 245 async fn plc_directory_unreachable_renders_network_error() { 328 - let http = FakeHttpClient::new(); 329 - let dns = FakeDnsResolver::new(); 246 + let http = common::FakeHttpClient::new(); 247 + let dns = common::FakeDnsResolver::new(); 330 248 331 249 // Don't add any response for PLC directory or DNS resolver - causes network error. 332 250 ··· 354 272 355 273 #[tokio::test] 356 274 async fn missing_labeler_record_renders_404_distinct_from_transport() { 357 - let http = FakeHttpClient::new(); 358 - let dns = FakeDnsResolver::new(); 275 + let http = common::FakeHttpClient::new(); 276 + let dns = common::FakeDnsResolver::new(); 359 277 360 278 // Mock DID resolution with a valid healthy DID doc. 361 279 let did_json = include_bytes!("fixtures/labeler/identity/healthy_plc/did.json").to_vec(); 362 280 http.add_response( 363 - "https://plc.directory/did:plc:test123456789abcdefghijklmnop", 281 + &Url::parse("https://plc.directory/did:plc:test123456789abcdefghijklmnop").unwrap(), 364 282 200, 365 283 did_json, 366 284 ); 367 285 368 286 // Mock 404 for labeler record (the actual test case). 369 - http.add_response( 370 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self", 287 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:test123456789abcdefghijklmnop&collection=app.bsky.labeler.service&rkey=self").unwrap(), 371 288 404, 372 289 b"Not found".to_vec(), 373 290 ); ··· 394 311 395 312 #[tokio::test] 396 313 async fn missing_service_renders_spec_violation_with_span() { 397 - let http = FakeHttpClient::new(); 398 - let dns = FakeDnsResolver::new(); 314 + let http = common::FakeHttpClient::new(); 315 + let dns = common::FakeDnsResolver::new(); 399 316 400 317 // Mock DID resolution with missing labeler service. 401 318 let did_json = include_bytes!("fixtures/labeler/identity/missing_service/did.json").to_vec(); 402 319 http.add_response( 403 - "https://plc.directory/did:plc:missing_service_test_123456789", 320 + &Url::parse("https://plc.directory/did:plc:missing_service_test_123456789").unwrap(), 404 321 200, 405 322 did_json, 406 323 ); ··· 408 325 // Mock labeler record fetch (should not be reached). 409 326 let labeler_record_json = 410 327 include_bytes!("fixtures/labeler/identity/missing_service/labeler_record.json").to_vec(); 411 - http.add_response( 412 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:missing_service_test_123456789&collection=app.bsky.labeler.service&rkey=self", 328 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:missing_service_test_123456789&collection=app.bsky.labeler.service&rkey=self").unwrap(), 413 329 200, 414 330 labeler_record_json, 415 331 ); ··· 439 355 440 356 #[tokio::test] 441 357 async fn missing_signing_key_renders_spec_violation() { 442 - let http = FakeHttpClient::new(); 443 - let dns = FakeDnsResolver::new(); 358 + let http = common::FakeHttpClient::new(); 359 + let dns = common::FakeDnsResolver::new(); 444 360 445 361 // Mock DID resolution with missing signing key. 446 362 let did_json = 447 363 include_bytes!("fixtures/labeler/identity/missing_signing_key/did.json").to_vec(); 448 364 http.add_response( 449 - "https://plc.directory/did:plc:missing_signing_key_test_12345", 365 + &Url::parse("https://plc.directory/did:plc:missing_signing_key_test_12345").unwrap(), 450 366 200, 451 367 did_json, 452 368 ); ··· 455 371 let labeler_record_json = 456 372 include_bytes!("fixtures/labeler/identity/missing_signing_key/labeler_record.json") 457 373 .to_vec(); 458 - http.add_response( 459 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:missing_signing_key_test_12345&collection=app.bsky.labeler.service&rkey=self", 374 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:missing_signing_key_test_12345&collection=app.bsky.labeler.service&rkey=self").unwrap(), 460 375 200, 461 376 labeler_record_json, 462 377 ); ··· 486 401 487 402 #[tokio::test] 488 403 async fn non_https_endpoint_renders_spec_violation() { 489 - let http = FakeHttpClient::new(); 490 - let dns = FakeDnsResolver::new(); 404 + let http = common::FakeHttpClient::new(); 405 + let dns = common::FakeDnsResolver::new(); 491 406 492 407 // Mock DID resolution with non-HTTPS endpoint. 493 408 let did_json = include_bytes!("fixtures/labeler/identity/non_https_endpoint/did.json").to_vec(); 494 409 http.add_response( 495 - "https://plc.directory/did:plc:non_https_endpoint_test_123456", 410 + &Url::parse("https://plc.directory/did:plc:non_https_endpoint_test_123456").unwrap(), 496 411 200, 497 412 did_json, 498 413 ); ··· 500 415 // Mock labeler record fetch (should not be reached). 501 416 let labeler_record_json = 502 417 include_bytes!("fixtures/labeler/identity/non_https_endpoint/labeler_record.json").to_vec(); 503 - http.add_response( 504 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:non_https_endpoint_test_123456&collection=app.bsky.labeler.service&rkey=self", 418 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:non_https_endpoint_test_123456&collection=app.bsky.labeler.service&rkey=self").unwrap(), 505 419 200, 506 420 labeler_record_json, 507 421 ); ··· 531 445 532 446 #[tokio::test] 533 447 async fn empty_policies_renders_spec_violation_with_span() { 534 - let http = FakeHttpClient::new(); 535 - let dns = FakeDnsResolver::new(); 448 + let http = common::FakeHttpClient::new(); 449 + let dns = common::FakeDnsResolver::new(); 536 450 537 451 // Mock DID resolution. 538 452 let did_json = include_bytes!("fixtures/labeler/identity/empty_policies/did.json").to_vec(); 539 453 http.add_response( 540 - "https://plc.directory/did:plc:empty_policies_test_123456789ab", 454 + &Url::parse("https://plc.directory/did:plc:empty_policies_test_123456789ab").unwrap(), 541 455 200, 542 456 did_json, 543 457 ); ··· 545 459 // Mock labeler record fetch with empty policies. 546 460 let labeler_record_json = 547 461 include_bytes!("fixtures/labeler/identity/empty_policies/labeler_record.json").to_vec(); 548 - http.add_response( 549 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:empty_policies_test_123456789ab&collection=app.bsky.labeler.service&rkey=self", 462 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:empty_policies_test_123456789ab&collection=app.bsky.labeler.service&rkey=self").unwrap(), 550 463 200, 551 464 labeler_record_json, 552 465 ); ··· 576 489 577 490 #[tokio::test] 578 491 async fn endpoint_mismatch_spec_violation() { 579 - let http = FakeHttpClient::new(); 580 - let dns = FakeDnsResolver::new(); 492 + let http = common::FakeHttpClient::new(); 493 + let dns = common::FakeDnsResolver::new(); 581 494 582 495 // Mock DID resolution. 583 496 let did_json = include_bytes!("fixtures/labeler/identity/endpoint_mismatch/did.json").to_vec(); 584 497 http.add_response( 585 - "https://plc.directory/did:plc:endpoint_mismatch_test_123456789", 498 + &Url::parse("https://plc.directory/did:plc:endpoint_mismatch_test_123456789").unwrap(), 586 499 200, 587 500 did_json, 588 501 ); ··· 590 503 // Mock labeler record fetch. 591 504 let labeler_record_json = 592 505 include_bytes!("fixtures/labeler/identity/endpoint_mismatch/labeler_record.json").to_vec(); 593 - http.add_response( 594 - "https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:endpoint_mismatch_test_123456789&collection=app.bsky.labeler.service&rkey=self", 506 + http.add_response(&Url::parse("https://pds.example.com/xrpc/com.atproto.repo.getRecord?repo=did:plc:endpoint_mismatch_test_123456789&collection=app.bsky.labeler.service&rkey=self").unwrap(), 595 507 200, 596 508 labeler_record_json, 597 509 );