2026-09-19 20:31:14 +02:00
|
|
|
package router
|
|
|
|
|
|
|
|
|
|
import (
|
2026-09-19 21:58:24 +02:00
|
|
|
"bytes"
|
2026-09-19 20:31:14 +02:00
|
|
|
"context"
|
2026-10-02 23:29:34 +02:00
|
|
|
"database/sql"
|
|
|
|
|
"database/sql/driver"
|
2026-09-19 21:58:24 +02:00
|
|
|
"encoding/json"
|
2026-10-02 23:29:34 +02:00
|
|
|
"io"
|
2026-09-19 20:31:14 +02:00
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
2026-10-03 00:14:23 +02:00
|
|
|
"strings"
|
2026-09-19 20:31:14 +02:00
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-10-03 00:14:23 +02:00
|
|
|
"github.com/danyel/go-guess/backend/internal/config"
|
2026-09-19 21:58:24 +02:00
|
|
|
"github.com/danyel/go-guess/backend/internal/eventbus"
|
2026-10-03 00:14:23 +02:00
|
|
|
"github.com/danyel/go-guess/backend/internal/golooseauth"
|
2026-09-19 20:31:14 +02:00
|
|
|
"github.com/danyel/go-guess/backend/internal/security"
|
|
|
|
|
"github.com/danyel/go-guess/backend/internal/service"
|
|
|
|
|
"github.com/danyel/go-guess/backend/internal/service/model"
|
|
|
|
|
"github.com/danyel/go-guess/backend/internal/web/handler"
|
2026-10-02 23:29:34 +02:00
|
|
|
"github.com/danyel/go-loose/client"
|
|
|
|
|
"gorm.io/driver/postgres"
|
|
|
|
|
"gorm.io/gorm"
|
2026-09-19 20:31:14 +02:00
|
|
|
)
|
|
|
|
|
|
2026-10-02 23:29:34 +02:00
|
|
|
const (
|
|
|
|
|
testTenant = "ypto"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
sql.Register("router-stub", stubDriver{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func openStubDB(t *testing.T) *gorm.DB {
|
|
|
|
|
t.Helper()
|
|
|
|
|
sqlDB, err := sql.Open("router-stub", "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
|
|
|
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{DisableAutomaticPing: true})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return db
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTestHandler(t *testing.T, interviews service.IScheduledInterviewService) *handler.Handler {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if interviews == nil {
|
|
|
|
|
interviews = interviewStub{}
|
|
|
|
|
}
|
|
|
|
|
return handler.New(
|
|
|
|
|
openStubDB(t),
|
|
|
|
|
authStub{}, jobStub{}, questionStub{}, participantStub{}, invitationStub{},
|
|
|
|
|
userStub{}, interviews, 10, "http://localhost:5173",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func withTenant(request *http.Request) {
|
|
|
|
|
request.Header.Set(client.XTenantId, testTenant)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 20:31:14 +02:00
|
|
|
type authStub struct{}
|
|
|
|
|
|
|
|
|
|
func (authStub) Login(context.Context, string, string) (string, model.User, error) {
|
|
|
|
|
return "", model.User{}, service.ErrInvalidCredentials
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 00:14:23 +02:00
|
|
|
func (authStub) EstablishExternalSession(context.Context, string, string) (string, model.User, error) {
|
|
|
|
|
return "", model.User{}, service.ErrInvalidCredentials
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 20:31:14 +02:00
|
|
|
type jobStub struct{}
|
|
|
|
|
|
|
|
|
|
func (jobStub) List(context.Context) ([]model.JobPosting, error) { return []model.JobPosting{}, nil }
|
|
|
|
|
func (jobStub) Get(context.Context, uint) (model.JobPosting, error) { return model.JobPosting{}, nil }
|
|
|
|
|
func (jobStub) Create(context.Context, model.JobPosting) (model.JobPosting, error) {
|
|
|
|
|
return model.JobPosting{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (jobStub) Update(context.Context, uint, string, int) (model.JobPosting, error) {
|
|
|
|
|
return model.JobPosting{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (jobStub) AttachQuestion(context.Context, uint, uint) error { return nil }
|
|
|
|
|
func (jobStub) DetachQuestion(context.Context, uint, uint) error { return nil }
|
|
|
|
|
func (jobStub) Candidates(context.Context, uint) ([]model.CandidateMatch, error) {
|
|
|
|
|
return []model.CandidateMatch{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type participantStub struct{}
|
|
|
|
|
|
|
|
|
|
func (participantStub) List(context.Context) ([]model.Participant, error) { return nil, nil }
|
|
|
|
|
func (participantStub) Get(context.Context, uint) (model.Participant, error) {
|
|
|
|
|
return model.Participant{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (participantStub) Create(context.Context, model.Participant) (model.Participant, error) {
|
|
|
|
|
return model.Participant{}, nil
|
|
|
|
|
}
|
2026-10-01 17:36:43 +02:00
|
|
|
func (participantStub) Update(context.Context, model.Participant) (model.Participant, error) {
|
|
|
|
|
return model.Participant{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (participantStub) UpdateTraits(context.Context, uint) error { return nil }
|
2026-09-19 20:31:14 +02:00
|
|
|
|
|
|
|
|
type questionStub struct{}
|
|
|
|
|
|
|
|
|
|
func (questionStub) List(context.Context, string) ([]model.Question, error) { return nil, nil }
|
|
|
|
|
func (questionStub) Create(context.Context, model.Question) (model.Question, error) {
|
|
|
|
|
return model.Question{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (questionStub) Update(context.Context, uint, model.Question) (model.Question, error) {
|
|
|
|
|
return model.Question{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (questionStub) SetDeprecated(context.Context, uint, bool) (model.Question, error) {
|
|
|
|
|
return model.Question{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type invitationStub struct{}
|
|
|
|
|
|
|
|
|
|
func (invitationStub) List(context.Context, uint) ([]model.Invitation, error) { return nil, nil }
|
|
|
|
|
func (invitationStub) Create(context.Context, uint, uint) (model.Invitation, error) {
|
|
|
|
|
return model.Invitation{}, nil
|
|
|
|
|
}
|
2026-09-19 20:56:27 +02:00
|
|
|
func (invitationStub) Review(context.Context, uint, uint) (model.Interview, error) {
|
|
|
|
|
return model.Interview{}, nil
|
|
|
|
|
}
|
2026-09-19 20:31:14 +02:00
|
|
|
func (invitationStub) GetInterview(context.Context, string) (model.Interview, error) {
|
|
|
|
|
return model.Interview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (invitationStub) Accept(context.Context, string) (model.Interview, error) {
|
|
|
|
|
return model.Interview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (invitationStub) SaveAnswer(context.Context, string, uint, string) error { return nil }
|
|
|
|
|
func (invitationStub) Finish(context.Context, string) (model.Interview, error) {
|
|
|
|
|
return model.Interview{}, nil
|
|
|
|
|
}
|
2026-09-19 21:58:24 +02:00
|
|
|
func (invitationStub) SetOutcome(context.Context, uint, uint, string) (model.Invitation, error) {
|
|
|
|
|
return model.Invitation{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type userStub struct{}
|
|
|
|
|
|
|
|
|
|
func (userStub) List(context.Context) ([]model.User, error) { return nil, nil }
|
|
|
|
|
func (userStub) Create(context.Context, string, string, string) (model.User, error) {
|
|
|
|
|
return model.User{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type interviewStub struct{}
|
|
|
|
|
|
|
|
|
|
func (interviewStub) Create(
|
|
|
|
|
context.Context, uint, uint, model.ScheduledInterview, []uint,
|
|
|
|
|
) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) List(context.Context, uint, uint) ([]model.ScheduledInterview, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) Get(context.Context, uint, uint) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) GetParticipantMeeting(context.Context, string) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) UpdateStatus(context.Context, uint, uint, string) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) UpdateDocument(context.Context, uint, uint, string) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) ListNotes(context.Context, uint, uint) ([]model.InterviewNote, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) CreateNote(context.Context, uint, uint, string) (model.InterviewNote, error) {
|
|
|
|
|
return model.InterviewNote{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) ListInbox(context.Context, uint) ([]model.ScheduledInterview, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) UpdateInbox(context.Context, uint, uint, string) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) ListCalendar(context.Context, uint) ([]model.ScheduledInterview, error) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
2026-09-19 22:22:42 +02:00
|
|
|
func (interviewStub) Subscribe(context.Context, uint, uint) (<-chan eventbus.InterviewEvent, error) {
|
|
|
|
|
return make(chan eventbus.InterviewEvent), nil
|
|
|
|
|
}
|
|
|
|
|
func (interviewStub) SubscribeParticipant(context.Context, string) (<-chan eventbus.InterviewEvent, error) {
|
|
|
|
|
return make(chan eventbus.InterviewEvent), nil
|
2026-09-19 21:58:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type interviewCapture struct {
|
|
|
|
|
interviewStub
|
|
|
|
|
userID uint
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *interviewCapture) Get(
|
|
|
|
|
_ context.Context, userID, interviewID uint,
|
|
|
|
|
) (model.ScheduledInterview, error) {
|
|
|
|
|
s.userID = userID
|
|
|
|
|
return model.ScheduledInterview{
|
|
|
|
|
ID: interviewID,
|
|
|
|
|
Attendees: []model.InterviewAttendee{{
|
|
|
|
|
ID: 3, UserID: userID, User: model.User{ID: userID, DisplayName: "Interviewer"},
|
|
|
|
|
}},
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *interviewCapture) GetParticipantMeeting(
|
|
|
|
|
context.Context, string,
|
|
|
|
|
) (model.ScheduledInterview, error) {
|
|
|
|
|
return model.ScheduledInterview{
|
|
|
|
|
ID: 4, JobID: 5, Job: model.JobPosting{Title: "Backend Engineer"},
|
|
|
|
|
Participant: model.Participant{FirstName: "Candidate", LastName: "Person", Email: "private@example.com"},
|
|
|
|
|
SharedDocument: "candidate document",
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
2026-09-19 20:31:14 +02:00
|
|
|
|
|
|
|
|
func TestHealthDoesNotRequireAuthentication(t *testing.T) {
|
|
|
|
|
tokens := security.NewTokenManager("01234567890123456789012345678901", time.Hour)
|
2026-10-02 23:29:34 +02:00
|
|
|
router := New(newTestHandler(t, nil), tokens, "http://localhost:5173")
|
2026-09-19 20:31:14 +02:00
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/health", nil)
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(response, request)
|
|
|
|
|
if response.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("expected 200, got %d", response.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProtectedRouteRequiresValidToken(t *testing.T) {
|
|
|
|
|
tokens := security.NewTokenManager("01234567890123456789012345678901", time.Hour)
|
2026-10-02 23:29:34 +02:00
|
|
|
router := New(newTestHandler(t, nil), tokens, "http://localhost:5173")
|
2026-09-19 20:31:14 +02:00
|
|
|
|
2026-10-02 23:29:34 +02:00
|
|
|
unauthorizedRequest := httptest.NewRequest(http.MethodGet, "/api/jobs", nil)
|
|
|
|
|
withTenant(unauthorizedRequest)
|
2026-09-19 20:31:14 +02:00
|
|
|
unauthorized := httptest.NewRecorder()
|
2026-10-02 23:29:34 +02:00
|
|
|
router.ServeHTTP(unauthorized, unauthorizedRequest)
|
2026-09-19 20:31:14 +02:00
|
|
|
if unauthorized.Code != http.StatusUnauthorized || unauthorized.Header().Get("Content-Type") != "application/json" {
|
2026-10-02 23:29:34 +02:00
|
|
|
t.Fatalf("expected JSON 401, got %d %q %s", unauthorized.Code, unauthorized.Header().Get("Content-Type"), unauthorized.Body.String())
|
2026-09-19 20:31:14 +02:00
|
|
|
}
|
|
|
|
|
token, err := tokens.Issue(1, "interviewer@example.com", "interviewer")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/jobs", nil)
|
2026-10-02 23:29:34 +02:00
|
|
|
withTenant(request)
|
2026-09-19 20:31:14 +02:00
|
|
|
request.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
|
authorized := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(authorized, request)
|
|
|
|
|
if authorized.Code != http.StatusOK {
|
2026-10-02 23:29:34 +02:00
|
|
|
t.Fatalf("expected 200, got %d %s", authorized.Code, authorized.Body.String())
|
2026-09-19 20:31:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 21:58:24 +02:00
|
|
|
|
|
|
|
|
func TestProtectedInterviewUsesAuthenticatedUserID(t *testing.T) {
|
|
|
|
|
interviews := &interviewCapture{}
|
|
|
|
|
tokens := security.NewTokenManager("01234567890123456789012345678901", time.Hour)
|
2026-10-02 23:29:34 +02:00
|
|
|
api := New(newTestHandler(t, interviews), tokens, "http://localhost:5173")
|
2026-09-19 21:58:24 +02:00
|
|
|
token, err := tokens.Issue(42, "co@example.com", "co_interviewer")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/scheduled-interviews/9", nil)
|
2026-10-02 23:29:34 +02:00
|
|
|
withTenant(request)
|
2026-09-19 21:58:24 +02:00
|
|
|
request.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
api.ServeHTTP(response, request)
|
|
|
|
|
if response.Code != http.StatusOK || interviews.userID != 42 {
|
2026-10-02 23:29:34 +02:00
|
|
|
t.Fatalf("expected authenticated user 42, got status=%d user=%d body=%s", response.Code, interviews.userID, response.Body.String())
|
2026-09-19 21:58:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestParticipantMeetingDoesNotExposeInternalCandidateData(t *testing.T) {
|
|
|
|
|
interviews := &interviewCapture{}
|
2026-10-02 23:29:34 +02:00
|
|
|
api := New(
|
|
|
|
|
newTestHandler(t, interviews),
|
|
|
|
|
security.NewTokenManager("01234567890123456789012345678901", time.Hour),
|
|
|
|
|
"http://localhost:5173",
|
2026-09-19 21:58:24 +02:00
|
|
|
)
|
2026-10-02 23:29:34 +02:00
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/participant-meetings/token", nil)
|
|
|
|
|
withTenant(request)
|
2026-09-19 21:58:24 +02:00
|
|
|
response := httptest.NewRecorder()
|
2026-10-02 23:29:34 +02:00
|
|
|
api.ServeHTTP(response, request)
|
2026-09-19 21:58:24 +02:00
|
|
|
if response.Code != http.StatusOK {
|
2026-10-02 23:29:34 +02:00
|
|
|
t.Fatalf("expected 200, got %d %s", response.Code, response.Body.String())
|
2026-09-19 21:58:24 +02:00
|
|
|
}
|
|
|
|
|
body := response.Body.Bytes()
|
|
|
|
|
if bytes.Contains(body, []byte("private@example.com")) ||
|
|
|
|
|
bytes.Contains(body, []byte("referenceAnswer")) ||
|
|
|
|
|
bytes.Contains(body, []byte("notes")) {
|
|
|
|
|
t.Fatalf("public response leaked internal data: %s", body)
|
|
|
|
|
}
|
|
|
|
|
var payload map[string]any
|
|
|
|
|
if err := json.Unmarshal(body, &payload); err != nil || payload["sharedDocument"] != "candidate document" {
|
|
|
|
|
t.Fatalf("unexpected public response: %s", body)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-02 23:29:34 +02:00
|
|
|
|
2026-10-03 00:14:23 +02:00
|
|
|
func TestOpenAPIDoesNotRequireTenant(t *testing.T) {
|
|
|
|
|
api := New(
|
|
|
|
|
newTestHandler(t, nil),
|
|
|
|
|
security.NewTokenManager("01234567890123456789012345678901", time.Hour),
|
|
|
|
|
"http://localhost:5173",
|
|
|
|
|
)
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
api.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/api/openapi.json", nil))
|
|
|
|
|
if response.Code != http.StatusOK || !bytes.Contains(response.Body.Bytes(), []byte(`"openapi"`)) {
|
|
|
|
|
t.Fatalf("expected OpenAPI document, got %d %s", response.Code, response.Body.Bytes())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTenantHostLoginRedirectsToMatchingGoLooseClient(t *testing.T) {
|
|
|
|
|
h := newTestHandler(t, nil)
|
|
|
|
|
browser, err := golooseauth.New(config.GoLooseConfig{
|
|
|
|
|
AuthDomain: "auth.dev",
|
|
|
|
|
AppDomain: "guess.dev",
|
|
|
|
|
Tenants: map[string]config.GoLooseTenant{
|
|
|
|
|
"nmbs": {ClientID: "glc_nmbs", ClientSecret: "secret"},
|
|
|
|
|
"ypto": {ClientID: "glc_ypto", ClientSecret: "secret"},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
h.SetBrowserAuth(browser)
|
|
|
|
|
api := New(h, security.NewTokenManager("01234567890123456789012345678901", time.Hour), "http://localhost:5173")
|
|
|
|
|
|
|
|
|
|
for _, slug := range []string{"nmbs", "ypto"} {
|
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
|
|
|
|
request.Host = slug + ".guess.dev"
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
api.ServeHTTP(response, request)
|
|
|
|
|
location := response.Header().Get("Location")
|
|
|
|
|
if response.Code != http.StatusFound || !strings.HasPrefix(location, "https://"+slug+".auth.dev/connect/authorize?") {
|
|
|
|
|
t.Fatalf("%s login redirect = %d %s", slug, response.Code, location)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(location, "redirect_uri=https%3A%2F%2F"+slug+".guess.dev%2Fapi%2Fauth%2Fcallback") {
|
|
|
|
|
t.Fatalf("%s callback missing from %s", slug, location)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 04:39:14 +02:00
|
|
|
func TestTenantHostLoginExplainsUnconfiguredGoLooseTenant(t *testing.T) {
|
|
|
|
|
h := newTestHandler(t, nil)
|
|
|
|
|
browser, err := golooseauth.New(config.GoLooseConfig{
|
|
|
|
|
AuthDomain: "auth.dev",
|
|
|
|
|
AppDomain: "guess.dev",
|
|
|
|
|
Tenants: map[string]config.GoLooseTenant{"nmbs": {ClientID: "glc_nmbs", ClientSecret: "secret"}},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
h.SetBrowserAuth(browser)
|
|
|
|
|
api := New(h, security.NewTokenManager("01234567890123456789012345678901", time.Hour), "http://localhost:5173")
|
|
|
|
|
|
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
|
|
|
|
request.Host = "ypto.guess.dev"
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
api.ServeHTTP(response, request)
|
|
|
|
|
if response.Code != http.StatusServiceUnavailable ||
|
|
|
|
|
!strings.Contains(response.Body.String(), "GO_LOOSE_YPTO_CLIENT_ID") {
|
|
|
|
|
t.Fatalf("ypto login = %d %s", response.Code, response.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
request = httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
|
|
|
|
request.Host = "guess.dev"
|
|
|
|
|
response = httptest.NewRecorder()
|
|
|
|
|
api.ServeHTTP(response, request)
|
|
|
|
|
if response.Code != http.StatusNotFound || !strings.Contains(response.Body.String(), "tenant") {
|
|
|
|
|
t.Fatalf("apex login = %d %s", response.Code, response.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoginWithoutGoLooseRegistryIsNotFound(t *testing.T) {
|
|
|
|
|
api := New(
|
|
|
|
|
newTestHandler(t, nil),
|
|
|
|
|
security.NewTokenManager("01234567890123456789012345678901", time.Hour),
|
|
|
|
|
"http://localhost:5173",
|
|
|
|
|
)
|
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
|
|
|
|
request.Host = "nmbs.guess.dev"
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
api.ServeHTTP(response, request)
|
|
|
|
|
if response.Code != http.StatusNotFound || !strings.Contains(response.Body.String(), "not enabled") {
|
|
|
|
|
t.Fatalf("login without registry = %d %s", response.Code, response.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-02 23:29:34 +02:00
|
|
|
type stubDriver struct{}
|
|
|
|
|
|
|
|
|
|
func (stubDriver) Open(string) (driver.Conn, error) { return stubConn{}, nil }
|
|
|
|
|
|
|
|
|
|
type stubConn struct{}
|
|
|
|
|
|
|
|
|
|
func (stubConn) Prepare(string) (driver.Stmt, error) { return stubStmt{}, nil }
|
|
|
|
|
func (stubConn) Close() error { return nil }
|
|
|
|
|
func (stubConn) Begin() (driver.Tx, error) { return stubTx{}, nil }
|
|
|
|
|
func (stubConn) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) {
|
|
|
|
|
return stubTx{}, nil
|
|
|
|
|
}
|
|
|
|
|
func (stubConn) ExecContext(context.Context, string, []driver.NamedValue) (driver.Result, error) {
|
|
|
|
|
return driver.RowsAffected(0), nil
|
|
|
|
|
}
|
|
|
|
|
func (stubConn) QueryContext(context.Context, string, []driver.NamedValue) (driver.Rows, error) {
|
|
|
|
|
return &stubRows{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type stubTx struct{}
|
|
|
|
|
|
|
|
|
|
func (stubTx) Commit() error { return nil }
|
|
|
|
|
func (stubTx) Rollback() error { return nil }
|
|
|
|
|
|
|
|
|
|
type stubStmt struct{}
|
|
|
|
|
|
|
|
|
|
func (stubStmt) Close() error { return nil }
|
|
|
|
|
func (stubStmt) NumInput() int { return -1 }
|
|
|
|
|
func (stubStmt) Exec([]driver.Value) (driver.Result, error) {
|
|
|
|
|
return driver.RowsAffected(0), nil
|
|
|
|
|
}
|
|
|
|
|
func (stubStmt) Query([]driver.Value) (driver.Rows, error) { return &stubRows{}, nil }
|
|
|
|
|
|
|
|
|
|
type stubRows struct{}
|
|
|
|
|
|
|
|
|
|
func (stubRows) Columns() []string { return nil }
|
|
|
|
|
func (stubRows) Close() error { return nil }
|
|
|
|
|
func (stubRows) Next([]driver.Value) error {
|
|
|
|
|
return io.EOF
|
|
|
|
|
}
|