package service import ( "context" "path/filepath" "testing" "time" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" statsv1 "github.com/bergabruh/stats-gateway/gen/stats/v1" "github.com/bergabruh/stats-gateway/internal/auth" "github.com/bergabruh/stats-gateway/internal/storage" ) // authedCtx returns a context with a valid Bearer JWT for the given subject. // Uses the same secret as newTestServer ("test-secret") and scope "tunnel" // (the only scope accepted by *auth.Verifier.Verify). func authedCtx(t *testing.T, sub string) context.Context { t.Helper() tok, err := auth.Issue([]byte("test-secret"), sub, "tunnel", time.Minute) if err != nil { t.Fatalf("issue token: %v", err) } return metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer "+tok) } func newRecordSpeedTestClient(t *testing.T) (statsv1.SpeedStatusClient, func()) { t.Helper() dir := t.TempDir() st, err := storage.Open(filepath.Join(dir, "test.db")) if err != nil { t.Fatalf("storage.Open: %v", err) } client, cleanup := newTestServer(t, st) return client, func() { cleanup(); st.Close() } } func TestRecordSpeedTest_HappyPath(t *testing.T) { client, cleanup := newRecordSpeedTestClient(t) defer cleanup() ctx := authedCtx(t, "fl-speedtest-sync") resp, err := client.RecordSpeedTest(ctx, &statsv1.RecordSpeedTestRequest{ Timestamp: time.Now().Unix(), Node: "fl", Country: "RU", DlMbps: 100, UlMbps: 20, PingMs: 35, JitterMs: 2, }) if err != nil { t.Fatalf("RecordSpeedTest: %v", err) } if resp.Id <= 0 { t.Fatalf("expected id>0, got %d", resp.Id) } } func TestRecordSpeedTest_RejectsBadNode(t *testing.T) { client, cleanup := newRecordSpeedTestClient(t) defer cleanup() ctx := authedCtx(t, "fl-speedtest-sync") _, err := client.RecordSpeedTest(ctx, &statsv1.RecordSpeedTestRequest{ Timestamp: time.Now().Unix(), Node: "moon", Country: "RU", DlMbps: 1, UlMbps: 1, PingMs: 1, JitterMs: 1, }) if err == nil { t.Fatalf("expected InvalidArgument for bad node, got nil") } if st, ok := status.FromError(err); !ok || st.Code() != codes.InvalidArgument { t.Fatalf("expected InvalidArgument, got %v", err) } } func TestRecordSpeedTest_RejectsNegativeMbps(t *testing.T) { client, cleanup := newRecordSpeedTestClient(t) defer cleanup() ctx := authedCtx(t, "fl-speedtest-sync") _, err := client.RecordSpeedTest(ctx, &statsv1.RecordSpeedTestRequest{ Timestamp: time.Now().Unix(), Node: "fl", Country: "RU", DlMbps: -1, UlMbps: 1, PingMs: 1, JitterMs: 1, }) if err == nil { t.Fatalf("expected InvalidArgument for negative dl_mbps, got nil") } if st, ok := status.FromError(err); !ok || st.Code() != codes.InvalidArgument { t.Fatalf("expected InvalidArgument, got %v", err) } } func TestRecordSpeedTest_RequiresAuth(t *testing.T) { client, cleanup := newRecordSpeedTestClient(t) defer cleanup() _, err := client.RecordSpeedTest(context.Background(), &statsv1.RecordSpeedTestRequest{ Timestamp: time.Now().Unix(), Node: "fl", Country: "RU", DlMbps: 1, UlMbps: 1, PingMs: 1, JitterMs: 1, }) if err == nil { t.Fatalf("expected Unauthenticated, got nil") } if st, ok := status.FromError(err); !ok || st.Code() != codes.Unauthenticated { t.Fatalf("expected Unauthenticated, got %v", err) } }