package atproto import ( "strings" "testing" ) // TestEndpointsFormat validates that all endpoint constants follow the XRPC convention func TestEndpointsFormat(t *testing.T) { tests := []struct { name string endpoint string prefix string // Expected namespace prefix (e.g., "io.atcr" or "com.atproto") }{ // Hold service multipart upload endpoints {"HoldInitiateUpload", HoldInitiateUpload, "io.atcr.hold"}, {"HoldGetPartUploadURL", HoldGetPartUploadURL, "io.atcr.hold"}, {"HoldUploadPart", HoldUploadPart, "io.atcr.hold"}, {"HoldCompleteUpload", HoldCompleteUpload, "io.atcr.hold"}, {"HoldAbortUpload", HoldAbortUpload, "io.atcr.hold"}, {"HoldNotifyManifest", HoldNotifyManifest, "io.atcr.hold"}, // Hold service crew management endpoints {"HoldRequestCrew", HoldRequestCrew, "io.atcr.hold"}, // ATProto sync endpoints {"SyncGetBlob", SyncGetBlob, "com.atproto.sync"}, {"SyncGetRepo", SyncGetRepo, "com.atproto.sync"}, {"SyncGetRecord", SyncGetRecord, "com.atproto.sync"}, {"SyncListRepos", SyncListRepos, "com.atproto.sync"}, {"SyncListReposByCollection", SyncListReposByCollection, "com.atproto.sync"}, {"SyncSubscribeRepos", SyncSubscribeRepos, "com.atproto.sync"}, {"SyncGetRepoStatus", SyncGetRepoStatus, "com.atproto.sync"}, {"SyncRequestCrawl", SyncRequestCrawl, "com.atproto.sync"}, // ATProto server endpoints {"ServerGetServiceAuth", ServerGetServiceAuth, "com.atproto.server"}, {"ServerDescribeServer", ServerDescribeServer, "com.atproto.server"}, {"ServerCreateSession", ServerCreateSession, "com.atproto.server"}, {"ServerRefreshSession", ServerRefreshSession, "com.atproto.server"}, {"ServerGetSession", ServerGetSession, "com.atproto.server"}, // ATProto repo endpoints {"RepoDescribeRepo", RepoDescribeRepo, "com.atproto.repo"}, {"RepoPutRecord", RepoPutRecord, "com.atproto.repo"}, {"RepoGetRecord", RepoGetRecord, "com.atproto.repo"}, {"RepoListRecords", RepoListRecords, "com.atproto.repo"}, {"RepoDeleteRecord", RepoDeleteRecord, "com.atproto.repo"}, {"RepoUploadBlob", RepoUploadBlob, "com.atproto.repo"}, // ATProto identity endpoints {"IdentityResolveHandle", IdentityResolveHandle, "com.atproto.identity"}, // Bluesky app endpoints {"ActorGetProfile", ActorGetProfile, "app.bsky.actor"}, {"ActorGetProfiles", ActorGetProfiles, "app.bsky.actor"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Check that endpoint starts with /xrpc/ if !strings.HasPrefix(tt.endpoint, "/xrpc/") { t.Errorf("%s = %q, does not start with /xrpc/", tt.name, tt.endpoint) } // Check that endpoint contains the expected namespace prefix if !strings.Contains(tt.endpoint, tt.prefix) { t.Errorf("%s = %q, does not contain expected prefix %q", tt.name, tt.endpoint, tt.prefix) } // Check that endpoint is not empty if tt.endpoint == "" { t.Errorf("%s is empty", tt.name) } // Check that endpoint follows naming convention: /xrpc/{namespace}.{method} // Should have at least 3 parts after /xrpc/: namespace.namespace.method parts := strings.Split(strings.TrimPrefix(tt.endpoint, "/xrpc/"), ".") if len(parts) < 3 { t.Errorf("%s = %q, does not follow XRPC convention (expected at least 3 dot-separated parts)", tt.name, tt.endpoint) } // Check that method name (last part) is camelCase and not empty method := parts[len(parts)-1] if method == "" { t.Errorf("%s = %q, has empty method name", tt.name, tt.endpoint) } if !isLowerCamelCase(method) { t.Errorf("%s = %q, method %q is not in camelCase", tt.name, tt.endpoint, method) } }) } } // TestEndpointUniqueness ensures no duplicate endpoint paths func TestEndpointUniqueness(t *testing.T) { endpoints := []string{ HoldInitiateUpload, HoldGetPartUploadURL, HoldUploadPart, HoldCompleteUpload, HoldAbortUpload, HoldNotifyManifest, HoldRequestCrew, SyncGetBlob, SyncGetRepo, SyncGetRecord, SyncListRepos, SyncListReposByCollection, SyncSubscribeRepos, SyncGetRepoStatus, SyncRequestCrawl, ServerGetServiceAuth, ServerDescribeServer, ServerCreateSession, ServerRefreshSession, ServerGetSession, RepoDescribeRepo, RepoPutRecord, RepoGetRecord, RepoListRecords, RepoDeleteRecord, RepoUploadBlob, IdentityResolveHandle, ActorGetProfile, ActorGetProfiles, } seen := make(map[string]bool) for _, endpoint := range endpoints { if seen[endpoint] { t.Errorf("Duplicate endpoint found: %q", endpoint) } seen[endpoint] = true } } // TestEndpointNamespaces validates that endpoints are correctly grouped by namespace func TestEndpointNamespaces(t *testing.T) { tests := []struct { name string endpoints []string namespace string }{ { name: "io.atcr.hold namespace", endpoints: []string{ HoldInitiateUpload, HoldGetPartUploadURL, HoldUploadPart, HoldCompleteUpload, HoldAbortUpload, HoldNotifyManifest, HoldRequestCrew, }, namespace: "io.atcr.hold", }, { name: "com.atproto.sync namespace", endpoints: []string{ SyncGetBlob, SyncGetRepo, SyncGetRecord, SyncListRepos, SyncListReposByCollection, SyncSubscribeRepos, SyncGetRepoStatus, SyncRequestCrawl, }, namespace: "com.atproto.sync", }, { name: "com.atproto.server namespace", endpoints: []string{ ServerGetServiceAuth, ServerDescribeServer, ServerCreateSession, ServerRefreshSession, ServerGetSession, }, namespace: "com.atproto.server", }, { name: "com.atproto.repo namespace", endpoints: []string{ RepoDescribeRepo, RepoPutRecord, RepoGetRecord, RepoListRecords, RepoDeleteRecord, RepoUploadBlob, }, namespace: "com.atproto.repo", }, { name: "com.atproto.identity namespace", endpoints: []string{ IdentityResolveHandle, }, namespace: "com.atproto.identity", }, { name: "app.bsky.actor namespace", endpoints: []string{ ActorGetProfile, ActorGetProfiles, }, namespace: "app.bsky.actor", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { for _, endpoint := range tt.endpoints { if !strings.Contains(endpoint, tt.namespace) { t.Errorf("Endpoint %q should be in namespace %q", endpoint, tt.namespace) } } }) } } // TestSpecificEndpoints validates specific endpoint paths are correct func TestSpecificEndpoints(t *testing.T) { tests := []struct { name string got string expected string }{ // Spot check a few critical endpoints {"HoldInitiateUpload", HoldInitiateUpload, "/xrpc/io.atcr.hold.initiateUpload"}, {"SyncGetBlob", SyncGetBlob, "/xrpc/com.atproto.sync.getBlob"}, {"ServerGetServiceAuth", ServerGetServiceAuth, "/xrpc/com.atproto.server.getServiceAuth"}, {"RepoPutRecord", RepoPutRecord, "/xrpc/com.atproto.repo.putRecord"}, {"IdentityResolveHandle", IdentityResolveHandle, "/xrpc/com.atproto.identity.resolveHandle"}, {"ActorGetProfile", ActorGetProfile, "/xrpc/app.bsky.actor.getProfile"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.got != tt.expected { t.Errorf("%s = %q, expected %q", tt.name, tt.got, tt.expected) } }) } } // isLowerCamelCase checks if a string follows lowerCamelCase convention func isLowerCamelCase(s string) bool { if len(s) == 0 { return false } // First character should be lowercase if s[0] < 'a' || s[0] > 'z' { return false } // Should not contain underscores or hyphens (common in other naming conventions) if strings.Contains(s, "_") || strings.Contains(s, "-") { return false } return true }