Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions spx-backend/cmd/spx-backend/auth_final_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package main

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/goplus/builder/spx-backend/internal/authn"
"github.com/goplus/builder/spx-backend/internal/model"
"github.com/goplus/yap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func createTestUser() *model.User {
return &model.User{
Model: model.Model{
ID: 1,
},
Username: "testuser",
}
}
Comment on lines +17 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace hard-coded test values with randomized data to improve test isolation and prevent potential conflicts between test runs. Add import for 'math/rand' and 'fmt' at the top of the file.

Suggested change
func createTestUser() *model.User {
return &model.User{
Model: model.Model{
ID: 1,
},
Username: "testuser",
}
}
func createTestUser() *model.User {
return &model.User{
Model: model.Model{
ID: uint(rand.Intn(1000) + 1),
},
Username: fmt.Sprintf("testuser_%d", rand.Intn(10000)),
}
}


func TestEnsureAuthenticatedUserFunction(t *testing.T) {
t.Run("WithAuthenticatedUser", func(t *testing.T) {
user := createTestUser()
ctx := authn.NewContextWithUser(context.Background(), user)
recorder := httptest.NewRecorder()

// Create yap context similar to how it's done in the .yap files
yapCtx := &yap.Context{}
yapCtx.Request = httptest.NewRequest("GET", "/test", nil).WithContext(ctx)
yapCtx.ResponseWriter = recorder

mUser, ok := ensureAuthenticatedUser(yapCtx)

assert.True(t, ok, "Should return true for authenticated user")
assert.Equal(t, user, mUser, "Should return the same user")
// When user is authenticated, no response should be written by ensureAuthenticatedUser
// But yap might set a default 200 status, so we check it didn't write an error
assert.NotEqual(t, http.StatusUnauthorized, recorder.Code, "Should not write unauthorized response")
})

t.Run("WithoutAuthenticatedUser", func(t *testing.T) {
ctx := context.Background()
recorder := httptest.NewRecorder()

// Create yap context without user
yapCtx := &yap.Context{}
yapCtx.Request = httptest.NewRequest("GET", "/test", nil).WithContext(ctx)
yapCtx.ResponseWriter = recorder

mUser, ok := ensureAuthenticatedUser(yapCtx)

assert.False(t, ok, "Should return false for unauthenticated user")
assert.Nil(t, mUser, "Should return nil user")

// Check that unauthorized response was written
assert.Equal(t, http.StatusUnauthorized, recorder.Code)
assert.Equal(t, "Bearer", recorder.Header().Get("WWW-Authenticate"))

// Check error response body
var errorResp errorPayload
err := json.Unmarshal(recorder.Body.Bytes(), &errorResp)
require.NoError(t, err)
assert.Equal(t, errorUnauthorized, errorResp.Code)
assert.Equal(t, "Unauthorized", errorResp.Msg)
})
}

func TestErrorHandling(t *testing.T) {
tests := []struct {
name string
code errorCode
expectedHTTP int
expectedMsg string
expectWWWAuth bool
}{
{
name: "Unauthorized",
code: errorUnauthorized,
expectedHTTP: 401,
expectedMsg: "Unauthorized",
expectWWWAuth: true,
},
{
name: "Forbidden",
code: errorForbidden,
expectedHTTP: 403,
expectedMsg: "Forbidden",
expectWWWAuth: false,
},
{
name: "NotFound",
code: errorNotFound,
expectedHTTP: 404,
expectedMsg: "Not found",
expectWWWAuth: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
recorder := httptest.NewRecorder()
yapCtx := &yap.Context{}
yapCtx.ResponseWriter = recorder

replyWithCode(yapCtx, tt.code)

assert.Equal(t, tt.expectedHTTP, recorder.Code)

if tt.expectWWWAuth {
assert.Equal(t, "Bearer", recorder.Header().Get("WWW-Authenticate"))
} else {
assert.Empty(t, recorder.Header().Get("WWW-Authenticate"))
}

var errorResp errorPayload
err := json.Unmarshal(recorder.Body.Bytes(), &errorResp)
require.NoError(t, err)
assert.Equal(t, tt.code, errorResp.Code)
assert.Equal(t, tt.expectedMsg, errorResp.Msg)
})
}
}

// Test that demonstrates the authentication flow as used in the .yap files
func TestYapFileAuthFlow(t *testing.T) {
t.Run("SimulatePostImageYapWithAuth", func(t *testing.T) {
user := createTestUser()
ctx := authn.NewContextWithUser(context.Background(), user)

req := httptest.NewRequest("POST", "/image", nil)
req = req.WithContext(ctx)
recorder := httptest.NewRecorder()

// Simulate the exact flow from post_image.yap
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
yapCtx := &yap.Context{}
yapCtx.Request = r
yapCtx.ResponseWriter = w

// This is the exact check from our modified .yap files
if _, ok := ensureAuthenticatedUser(yapCtx); !ok {
return
}

// If we get here, authentication succeeded
// Simulate successful image generation response
response := map[string]interface{}{
"id": "test-image-123",
"svg_url": "https://example.com/image.svg",
"status": "success",
}

yapCtx.JSON(200, response)
})

handler.ServeHTTP(recorder, req)

assert.Equal(t, http.StatusOK, recorder.Code)

var response map[string]interface{}
err := json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "test-image-123", response["id"])
assert.Equal(t, "success", response["status"])
})

t.Run("SimulatePostImageYapWithoutAuth", func(t *testing.T) {
// No user in context - simulates unauthenticated request
req := httptest.NewRequest("POST", "/image", nil)
recorder := httptest.NewRecorder()

// Simulate the exact flow from post_image.yap
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
yapCtx := &yap.Context{}
yapCtx.Request = r
yapCtx.ResponseWriter = w

// This should fail and return early
if _, ok := ensureAuthenticatedUser(yapCtx); !ok {
return
}

// This should NOT be reached
t.Fatal("Handler should not reach this point without authentication")
})

handler.ServeHTTP(recorder, req)

assert.Equal(t, http.StatusUnauthorized, recorder.Code)
assert.Equal(t, "Bearer", recorder.Header().Get("WWW-Authenticate"))

var errorResp errorPayload
err := json.Unmarshal(recorder.Body.Bytes(), &errorResp)
require.NoError(t, err)
assert.Equal(t, errorUnauthorized, errorResp.Code)
})

}
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/get_themes.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

result, err := ctrl.GetThemes(ctx.Context())
if err != nil {
Expand Down
9 changes: 3 additions & 6 deletions spx-backend/cmd/spx-backend/post_ai_interaction_turn.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ import (
)

ctx := &Context

// FIXME: Uncomment the following line to ensure the user is authenticated once
// https://github.com/goplus/builder/issues/1673 is fixed.
// if _, ok := ensureAuthenticatedUser(ctx); !ok {
// return
// }
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.AIInteractionTurnParams{}
if !parseJSON(ctx, params) {
Expand Down
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/post_game_assets_complete.yap
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// POST /game-assets/complete

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

// 解析JSON请求体
var req struct {
Expand Down
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/post_image.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.GenerateImageParams{}
if !parseJSON(ctx, params) {
Expand Down
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/post_image_svg.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.GenerateSVGParams{}
if !parseJSON(ctx, params) {
Expand Down
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/post_images_feedback.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.ImageFeedbackParams{}
if !parseJSON(ctx, params) {
Expand Down
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/post_images_instant_recommend.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.InstantRecommendParams{}
if !parseJSON(ctx, params) {
Expand Down
4 changes: 3 additions & 1 deletion spx-backend/cmd/spx-backend/post_images_recommend.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

ctx := &Context

if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.ImageRecommendParams{}
if !parseJSON(ctx, params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.ProjectContextParams{}
if !parseJSON(ctx, params) {
Expand Down
3 changes: 3 additions & 0 deletions spx-backend/cmd/spx-backend/post_util_fileurls.yap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
)

ctx := &Context
if _, ok := ensureAuthenticatedUser(ctx); !ok {
return
}

params := &controller.MakeFileURLsParams{}
if !parseJSON(ctx, params) {
Expand Down
7 changes: 1 addition & 6 deletions spx-backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ require (
gorm.io/gorm v1.30.1
)

require (
github.com/google/uuid v1.6.0
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
)
require github.com/google/uuid v1.6.0

require (
filippo.io/edwards25519 v1.1.0 // indirect
Expand Down Expand Up @@ -53,7 +49,6 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.10.0 // indirect
Expand Down
6 changes: 0 additions & 6 deletions spx-backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,6 @@ github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down Expand Up @@ -241,8 +237,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
Expand Down
Loading