Files
Stow/backend/internal/api/server_test.go
T

136 lines
4.6 KiB
Go

package api
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"stow/backend/internal/store"
)
type fakeStore struct {
created bool
lastUser string
lastItem store.NewItem
}
func (f *fakeStore) CreateAnonymousSession(context.Context) (string, string, error) {
return "user-1", "token-1", nil
}
func (f *fakeStore) Authenticate(_ context.Context, token string) (string, error) {
if token != "token-1" {
return "", sql.ErrNoRows
}
return "user-1", nil
}
func (f *fakeStore) CreateItem(_ context.Context, userID string, item store.NewItem) (store.Item, error) {
f.created, f.lastUser, f.lastItem = true, userID, item
return store.Item{ID: "item-1", SourceType: item.SourceType, PrimaryType: item.PrimaryType, Title: item.Title, Summary: item.Summary, Status: "ready", Tags: item.Tags, CreatedAt: time.Now()}, nil
}
func (f *fakeStore) GetItem(context.Context, string, string) (store.Item, error) {
return store.Item{}, store.ErrNotFound
}
func (f *fakeStore) ListItems(context.Context, string, int) ([]store.Item, error) {
return []store.Item{}, nil
}
func (f *fakeStore) SearchItems(context.Context, string, string, int) ([]store.Item, error) {
return []store.Item{}, nil
}
func (f *fakeStore) UpdateItem(context.Context, string, string, store.ItemPatch) (store.Item, error) {
return store.Item{}, store.ErrNotFound
}
func (f *fakeStore) DeleteItem(context.Context, string, string) error { return store.ErrNotFound }
func TestItemsRequireSession(t *testing.T) {
fake := &fakeStore{}
request := httptest.NewRequest(http.MethodPost, "/v1/items", bytes.NewBufferString(`{"source_type":"manual_text","title":"咖啡店"}`))
response := httptest.NewRecorder()
New(fake).ServeHTTP(response, request)
if response.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want 401", response.Code)
}
if fake.created {
t.Fatal("item was created without authentication")
}
}
func TestCreateItemValidatesAndUsesAuthenticatedUser(t *testing.T) {
fake := &fakeStore{}
handler := New(fake)
for _, test := range []struct {
name, body string
want int
}{
{"invalid source", `{"source_type":"unknown","title":"咖啡店"}`, http.StatusBadRequest},
{"valid item", `{"source_type":"manual_text","title":"咖啡店","tags":["上海"]}`, http.StatusCreated},
} {
t.Run(test.name, func(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "/v1/items", bytes.NewBufferString(test.body))
request.Header.Set("Authorization", "Bearer token-1")
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
if response.Code != test.want {
t.Fatalf("status = %d, want %d: %s", response.Code, test.want, response.Body.String())
}
})
}
if !fake.created || fake.lastUser != "user-1" || fake.lastItem.Title != "咖啡店" {
t.Fatalf("wrong authenticated create: %+v", fake)
}
}
func TestAnonymousSessionReturnsToken(t *testing.T) {
response := httptest.NewRecorder()
New(&fakeStore{}).ServeHTTP(response, httptest.NewRequest(http.MethodPost, "/v1/session/anonymous", nil))
if response.Code != http.StatusCreated {
t.Fatalf("status = %d", response.Code)
}
var body struct {
Code string `json:"code"`
Message string `json:"message"`
Data map[string]string `json:"data"`
}
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
if body.Code != "ok" || body.Data["access_token"] != "token-1" || body.Data["token_type"] != "Bearer" {
t.Fatalf("unexpected response: %v", body)
}
}
func TestAllResponsesUseEnvelope(t *testing.T) {
handler := New(&fakeStore{})
for _, test := range []struct {
method string
path string
want int
}{
{http.MethodGet, "/v1/health", http.StatusOK},
{http.MethodGet, "/v1/items", http.StatusUnauthorized},
{http.MethodGet, "/missing", http.StatusNotFound},
{http.MethodPut, "/v1/health", http.StatusMethodNotAllowed},
} {
response := httptest.NewRecorder()
handler.ServeHTTP(response, httptest.NewRequest(test.method, test.path, nil))
if response.Code != test.want {
t.Fatalf("%s %s: status %d, want %d", test.method, test.path, response.Code, test.want)
}
var body struct {
Code string `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
t.Fatalf("%s %s: %v", test.method, test.path, err)
}
if body.Code == "" || body.Message == "" || body.Data == nil {
t.Fatalf("%s %s: invalid envelope: %s", test.method, test.path, response.Body.String())
}
}
}