mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-04-20 16:40:29 +00:00
269 lines
7.7 KiB
Go
269 lines
7.7 KiB
Go
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"},
|
|
{"SyncListBlobs", SyncListBlobs, "com.atproto.sync"},
|
|
{"SyncListRepos", SyncListRepos, "com.atproto.sync"},
|
|
{"SyncListReposByCollection", SyncListReposByCollection, "com.atproto.sync"},
|
|
{"SyncSubscribeRepos", SyncSubscribeRepos, "com.atproto.sync"},
|
|
{"SyncGetRepoStatus", SyncGetRepoStatus, "com.atproto.sync"},
|
|
{"SyncGetHostStatus", SyncGetHostStatus, "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,
|
|
SyncListBlobs,
|
|
SyncListRepos,
|
|
SyncListReposByCollection,
|
|
SyncSubscribeRepos,
|
|
SyncGetRepoStatus,
|
|
SyncGetHostStatus,
|
|
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,
|
|
SyncListBlobs,
|
|
SyncListRepos,
|
|
SyncListReposByCollection,
|
|
SyncSubscribeRepos,
|
|
SyncGetRepoStatus,
|
|
SyncGetHostStatus,
|
|
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
|
|
}
|