172 lines
5.6 KiB
Go
172 lines
5.6 KiB
Go
package publicapi_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/publicapi"
|
|
)
|
|
|
|
type authErrorFixture struct {
|
|
Status int `json:"status"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type authFixture struct {
|
|
Version int `json:"version"`
|
|
APIKeys string `json:"apiKeys"`
|
|
Clients []struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
} `json:"clients"`
|
|
AuthenticationCases []struct {
|
|
Name string `json:"name"`
|
|
Headers map[string]string `json:"headers"`
|
|
Expected *struct {
|
|
Client struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"key"`
|
|
} `json:"client"`
|
|
Owner string `json:"owner"`
|
|
} `json:"expected"`
|
|
Error *authErrorFixture `json:"error"`
|
|
} `json:"authenticationCases"`
|
|
OwnerCases []struct {
|
|
ID string `json:"id"`
|
|
Owner string `json:"owner"`
|
|
MaxPartLength int `json:"maxPartLength"`
|
|
} `json:"ownerCases"`
|
|
WorkerCases []struct {
|
|
Name string `json:"name"`
|
|
Production bool `json:"production"`
|
|
ConfiguredToken string `json:"configuredToken"`
|
|
Headers map[string]string `json:"headers"`
|
|
Allowed bool `json:"allowed"`
|
|
Error *authErrorFixture `json:"error"`
|
|
} `json:"workerCases"`
|
|
}
|
|
|
|
func TestParseClientsMatchesSharedContract(t *testing.T) {
|
|
fixture := loadAuthFixture(t)
|
|
clients := publicapi.ParseClients(fixture.APIKeys)
|
|
if len(clients) != len(fixture.Clients) {
|
|
t.Fatalf("len(ParseClients()) = %d, want %d", len(clients), len(fixture.Clients))
|
|
}
|
|
for index, want := range fixture.Clients {
|
|
if clients[index] != (publicapi.PublicClient{ID: want.ID, Key: want.Key}) {
|
|
t.Fatalf("client[%d] = %+v, want %+v", index, clients[index], want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAuthenticateMatchesSharedContract(t *testing.T) {
|
|
fixture := loadAuthFixture(t)
|
|
authenticator := publicapi.NewAuthenticator(publicapi.Config{APIKeys: fixture.APIKeys})
|
|
for _, testCase := range fixture.AuthenticationCases {
|
|
t.Run(testCase.Name, func(t *testing.T) {
|
|
request := httptest.NewRequest("GET", "/api/v1/generations", nil)
|
|
for name, value := range testCase.Headers {
|
|
request.Header.Set(name, value)
|
|
}
|
|
client, owner, err := authenticator.Authenticate(request)
|
|
if testCase.Error != nil {
|
|
assertAuthError(t, err, *testCase.Error)
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("Authenticate() error = %v", err)
|
|
}
|
|
wantClient := publicapi.PublicClient{ID: testCase.Expected.Client.ID, Key: testCase.Expected.Client.Key}
|
|
if client != wantClient || owner != testCase.Expected.Owner {
|
|
t.Fatalf("Authenticate() = (%+v, %q), want (%+v, %q)", client, owner, wantClient, testCase.Expected.Owner)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOwnerIDMatchesSharedContract(t *testing.T) {
|
|
fixture := loadAuthFixture(t)
|
|
for _, testCase := range fixture.OwnerCases {
|
|
t.Run(testCase.ID, func(t *testing.T) {
|
|
owner := publicapi.OwnerID(testCase.ID)
|
|
if owner != testCase.Owner {
|
|
t.Fatalf("OwnerID(%q) = %q, want %q", testCase.ID, owner, testCase.Owner)
|
|
}
|
|
if testCase.MaxPartLength > 0 && len(owner)-len("api:") != testCase.MaxPartLength {
|
|
t.Fatalf("owner part length = %d, want %d", len(owner)-len("api:"), testCase.MaxPartLength)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAssertInternalWorkerMatchesSharedContract(t *testing.T) {
|
|
fixture := loadAuthFixture(t)
|
|
for _, testCase := range fixture.WorkerCases {
|
|
t.Run(testCase.Name, func(t *testing.T) {
|
|
authenticator := publicapi.NewAuthenticator(publicapi.Config{
|
|
InternalWorkerToken: testCase.ConfiguredToken,
|
|
Production: testCase.Production,
|
|
})
|
|
request := httptest.NewRequest("POST", "/api/internal/worker/tick", nil)
|
|
for name, value := range testCase.Headers {
|
|
request.Header.Set(name, value)
|
|
}
|
|
err := authenticator.AssertInternalWorker(request)
|
|
if testCase.Error != nil {
|
|
assertAuthError(t, err, *testCase.Error)
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("AssertInternalWorker() error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAuthenticatorUsesOnlyInjectedConfig(t *testing.T) {
|
|
t.Setenv("ZHINIAN_API_KEYS", "environment:must-not-be-read")
|
|
t.Setenv("ZHINIAN_INTERNAL_WORKER_TOKEN", "environment-worker-token")
|
|
t.Setenv("NODE_ENV", "production")
|
|
|
|
authenticator := publicapi.NewAuthenticator(publicapi.Config{})
|
|
request := httptest.NewRequest("GET", "/", nil)
|
|
request.Header.Set("authorization", "Bearer must-not-be-read")
|
|
_, _, err := authenticator.Authenticate(request)
|
|
assertAuthError(t, err, authErrorFixture{Status: 401, Message: "Invalid API key."})
|
|
if err := authenticator.AssertInternalWorker(httptest.NewRequest("POST", "/", nil)); err != nil {
|
|
t.Fatalf("development bypass with injected zero config error = %v", err)
|
|
}
|
|
}
|
|
|
|
func assertAuthError(t *testing.T, err error, want authErrorFixture) {
|
|
t.Helper()
|
|
if err == nil {
|
|
t.Fatal("error = nil, want typed authentication error")
|
|
}
|
|
authError, ok := err.(*publicapi.AuthError)
|
|
if !ok {
|
|
t.Fatalf("error type = %T, want *publicapi.AuthError", err)
|
|
}
|
|
if authError.Status != want.Status || authError.Message != want.Message || authError.Error() != want.Message {
|
|
t.Fatalf("error = %+v, want status=%d message=%q", authError, want.Status, want.Message)
|
|
}
|
|
}
|
|
|
|
func loadAuthFixture(t *testing.T) authFixture {
|
|
t.Helper()
|
|
data, err := os.ReadFile("../../../contracts/auth/public-api-auth-v1.json")
|
|
if err != nil {
|
|
t.Fatalf("read public API auth fixture: %v", err)
|
|
}
|
|
var fixture authFixture
|
|
if err := json.Unmarshal(data, &fixture); err != nil {
|
|
t.Fatalf("decode public API auth fixture: %v", err)
|
|
}
|
|
if fixture.Version != 1 {
|
|
t.Fatalf("fixture version = %d, want 1", fixture.Version)
|
|
}
|
|
return fixture
|
|
}
|