136 lines
4.6 KiB
Go
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())
|
|
}
|
|
}
|
|
}
|