···
wantTyped: ErrBadRequest,
962
+
name: "409 maps to ErrConflict",
963
+
err: &atclient.APIError{StatusCode: 409, Name: "InvalidSwap", Message: "Record CID mismatch"},
964
+
operation: "putRecord",
965
+
wantTyped: ErrConflict,
name: "500 wraps without typed error",
err: &atclient.APIError{StatusCode: 500, Name: "InternalError", Message: "Server error"},
operation: "listRecords",
···
1182
+
// TestClient_PutRecord tests the PutRecord method with a mock server.
1183
+
func TestClient_PutRecord(t *testing.T) {
1184
+
tests := []struct {
1188
+
record map[string]any
1190
+
serverResponse map[string]any
1197
+
name: "successful put with swapRecord",
1198
+
collection: "social.coves.comment",
1199
+
rkey: "3kjzl5kcb2s2v",
1200
+
record: map[string]any{
1201
+
"$type": "social.coves.comment",
1202
+
"content": "Updated comment content",
1204
+
swapRecord: "bafyreigbtj4x7ip5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua",
1205
+
serverResponse: map[string]any{
1206
+
"uri": "at://did:plc:test/social.coves.comment/3kjzl5kcb2s2v",
1207
+
"cid": "bafyreihd4q3yqcfvnv5zlp6n4fqzh6z4p4m3mwc7vvr6k2j6y6v2a3b4c5",
1209
+
serverStatus: http.StatusOK,
1210
+
wantURI: "at://did:plc:test/social.coves.comment/3kjzl5kcb2s2v",
1211
+
wantCID: "bafyreihd4q3yqcfvnv5zlp6n4fqzh6z4p4m3mwc7vvr6k2j6y6v2a3b4c5",
1215
+
name: "successful put without swapRecord",
1216
+
collection: "social.coves.comment",
1217
+
rkey: "3kjzl5kcb2s2v",
1218
+
record: map[string]any{
1219
+
"$type": "social.coves.comment",
1220
+
"content": "Updated comment",
1223
+
serverResponse: map[string]any{
1224
+
"uri": "at://did:plc:test/social.coves.comment/3kjzl5kcb2s2v",
1225
+
"cid": "bafyreihd4q3yqcfvnv5zlp6n4fqzh6z4p4m3mwc7vvr6k2j6y6v2a3b4c5",
1227
+
serverStatus: http.StatusOK,
1228
+
wantURI: "at://did:plc:test/social.coves.comment/3kjzl5kcb2s2v",
1229
+
wantCID: "bafyreihd4q3yqcfvnv5zlp6n4fqzh6z4p4m3mwc7vvr6k2j6y6v2a3b4c5",
1233
+
name: "conflict error (409)",
1234
+
collection: "social.coves.comment",
1236
+
record: map[string]any{"$type": "social.coves.comment"},
1237
+
swapRecord: "bafyreigbtj4x7ip5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua",
1238
+
serverResponse: map[string]any{
1239
+
"error": "InvalidSwap",
1240
+
"message": "Record CID does not match",
1242
+
serverStatus: http.StatusConflict,
1246
+
name: "server error",
1247
+
collection: "social.coves.comment",
1249
+
record: map[string]any{"$type": "social.coves.comment"},
1251
+
serverResponse: map[string]any{
1252
+
"error": "InvalidRequest",
1253
+
"message": "Invalid record",
1255
+
serverStatus: http.StatusBadRequest,
1260
+
for _, tt := range tests {
1261
+
t.Run(tt.name, func(t *testing.T) {
1262
+
// Create mock server
1263
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1265
+
if r.Method != http.MethodPost {
1266
+
t.Errorf("expected POST request, got %s", r.Method)
1270
+
expectedPath := "/xrpc/com.atproto.repo.putRecord"
1271
+
if r.URL.Path != expectedPath {
1272
+
t.Errorf("path = %q, want %q", r.URL.Path, expectedPath)
1275
+
// Verify request body
1276
+
var payload map[string]any
1277
+
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
1278
+
t.Fatalf("failed to decode request body: %v", err)
1281
+
// Check required fields
1282
+
if payload["collection"] != tt.collection {
1283
+
t.Errorf("collection = %v, want %v", payload["collection"], tt.collection)
1285
+
if payload["rkey"] != tt.rkey {
1286
+
t.Errorf("rkey = %v, want %v", payload["rkey"], tt.rkey)
1289
+
// Check swapRecord inclusion
1290
+
if tt.swapRecord != "" {
1291
+
if payload["swapRecord"] != tt.swapRecord {
1292
+
t.Errorf("swapRecord = %v, want %v", payload["swapRecord"], tt.swapRecord)
1295
+
if _, exists := payload["swapRecord"]; exists {
1296
+
t.Error("swapRecord should not be included when empty")
1301
+
w.Header().Set("Content-Type", "application/json")
1302
+
w.WriteHeader(tt.serverStatus)
1303
+
json.NewEncoder(w).Encode(tt.serverResponse)
1305
+
defer server.Close()
1308
+
apiClient := atclient.NewAPIClient(server.URL)
1309
+
apiClient.Auth = &bearerAuth{token: "test-token"}
1312
+
apiClient: apiClient,
1313
+
did: "did:plc:test",
1317
+
// Execute PutRecord
1318
+
ctx := context.Background()
1319
+
uri, cid, err := c.PutRecord(ctx, tt.collection, tt.rkey, tt.record, tt.swapRecord)
1323
+
t.Fatal("expected error, got nil")
1329
+
t.Fatalf("unexpected error: %v", err)
1332
+
if uri != tt.wantURI {
1333
+
t.Errorf("uri = %q, want %q", uri, tt.wantURI)
1336
+
if cid != tt.wantCID {
1337
+
t.Errorf("cid = %q, want %q", cid, tt.wantCID)
1343
+
// TestClient_TypedErrors_PutRecord tests that PutRecord returns typed errors.
1344
+
func TestClient_TypedErrors_PutRecord(t *testing.T) {
1345
+
tests := []struct {
1351
+
name: "401 returns ErrUnauthorized",
1352
+
serverStatus: http.StatusUnauthorized,
1353
+
wantErr: ErrUnauthorized,
1356
+
name: "403 returns ErrForbidden",
1357
+
serverStatus: http.StatusForbidden,
1358
+
wantErr: ErrForbidden,
1361
+
name: "409 returns ErrConflict",
1362
+
serverStatus: http.StatusConflict,
1363
+
wantErr: ErrConflict,
1366
+
name: "400 returns ErrBadRequest",
1367
+
serverStatus: http.StatusBadRequest,
1368
+
wantErr: ErrBadRequest,
1372
+
for _, tt := range tests {
1373
+
t.Run(tt.name, func(t *testing.T) {
1374
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1375
+
w.Header().Set("Content-Type", "application/json")
1376
+
w.WriteHeader(tt.serverStatus)
1377
+
json.NewEncoder(w).Encode(map[string]any{
1378
+
"error": "TestError",
1379
+
"message": "Test error message",
1382
+
defer server.Close()
1384
+
apiClient := atclient.NewAPIClient(server.URL)
1385
+
apiClient.Auth = &bearerAuth{token: "test-token"}
1388
+
apiClient: apiClient,
1389
+
did: "did:plc:test",
1393
+
ctx := context.Background()
1394
+
_, _, err := c.PutRecord(ctx, "test.collection", "rkey", map[string]any{}, "")
1397
+
t.Fatal("expected error, got nil")
1400
+
if !errors.Is(err, tt.wantErr) {
1401
+
t.Errorf("expected errors.Is(%v, %v) to be true", err, tt.wantErr)