feat: backend service
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"galaxy/backend/internal/geo"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
type fakeAdminGeoLister struct {
|
||||
entries []geo.CountryCounter
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeAdminGeoLister) ListUserCounters(_ context.Context, _ uuid.UUID) ([]geo.CountryCounter, error) {
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
return f.entries, nil
|
||||
}
|
||||
|
||||
func newAdminGeoEngine(t *testing.T, fake AdminGeoLister) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
h := NewAdminGeoHandlers(fake, zaptest.NewLogger(t))
|
||||
r.GET("/api/v1/admin/geo/users/:user_id/countries", h.ListUserCountries())
|
||||
return r
|
||||
}
|
||||
|
||||
func TestAdminGeoListUserCountriesSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Date(2026, 5, 4, 12, 0, 0, 0, time.UTC)
|
||||
fake := &fakeAdminGeoLister{entries: []geo.CountryCounter{
|
||||
{Country: "AU", Count: 3, LastSeenAt: &now},
|
||||
{Country: "DE", Count: 1, LastSeenAt: nil},
|
||||
}}
|
||||
r := newAdminGeoEngine(t, fake)
|
||||
|
||||
userID := uuid.New()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+userID.String()+"/countries", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: want 200, got %d (%s)", rec.Code, rec.Body.String())
|
||||
}
|
||||
var body adminGeoListWire
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if body.UserID != userID.String() {
|
||||
t.Errorf("user_id: want %s, got %s", userID, body.UserID)
|
||||
}
|
||||
if len(body.Items) != 2 {
|
||||
t.Fatalf("items: want 2, got %d (%+v)", len(body.Items), body.Items)
|
||||
}
|
||||
if body.Items[0].Country != "AU" || body.Items[0].Count != 3 || body.Items[0].LastSeenAt == nil {
|
||||
t.Errorf("items[0] mismatch: %+v", body.Items[0])
|
||||
}
|
||||
if body.Items[1].Country != "DE" || body.Items[1].Count != 1 || body.Items[1].LastSeenAt != nil {
|
||||
t.Errorf("items[1] mismatch: %+v", body.Items[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminGeoListUserCountriesEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := newAdminGeoEngine(t, &fakeAdminGeoLister{})
|
||||
|
||||
userID := uuid.New()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+userID.String()+"/countries", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status: want 200, got %d", rec.Code)
|
||||
}
|
||||
var body adminGeoListWire
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if body.Items == nil {
|
||||
t.Fatal("items: want non-nil empty slice, got nil")
|
||||
}
|
||||
if len(body.Items) != 0 {
|
||||
t.Fatalf("items: want empty, got %+v", body.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminGeoListUserCountriesInvalidUserID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := newAdminGeoEngine(t, &fakeAdminGeoLister{})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/not-a-uuid/countries", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status: want 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminGeoListUserCountriesStoreError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := newAdminGeoEngine(t, &fakeAdminGeoLister{err: errors.New("boom")})
|
||||
|
||||
userID := uuid.New()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+userID.String()+"/countries", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("status: want 500, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminGeoListUserCountriesNilServiceReturns501(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
h := NewAdminGeoHandlers(nil, zaptest.NewLogger(t))
|
||||
r.GET("/api/v1/admin/geo/users/:user_id/countries", h.ListUserCountries())
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/geo/users/"+uuid.New().String()+"/countries", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotImplemented {
|
||||
t.Fatalf("status: want 501, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user