mirror of https://github.com/veypi/OneAuth.git
refactor(test): restructure integration tests for auth and permissions
- Move and split 'auth/auth_test.go' into the 'tests/' directory
- Add 'tests/main_test.go' for global test suite setup
- Add 'tests/helpers_test.go' for shared test utilities
- Create separate test files for different auth scenarios ('auth_test.go', 'none_auth_test.go')
- Add focused tests for org permissions and middleware ('org_permission_test.go', 'resource_perm_test.go', 'org_load_middleware_test.go')
master
parent
f7c4f1ee86
commit
01620b3185
@ -0,0 +1,86 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
// Ensure base users are created (Admin, User1, User2)
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// Test Temp User Lifecycle
|
||||||
|
tempUser := "temp_user"
|
||||||
|
tempPass := "password123"
|
||||||
|
tempEmail := "temp@test.com"
|
||||||
|
|
||||||
|
// 1. Register Temp User
|
||||||
|
t.Run("Register Temp User", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/auth/register", map[string]string{
|
||||||
|
"username": tempUser,
|
||||||
|
"password": tempPass,
|
||||||
|
"email": tempEmail,
|
||||||
|
}, "")
|
||||||
|
|
||||||
|
// If user exists from previous run, that's fine, but in clean run it should be 200
|
||||||
|
if resp.Code != 200 {
|
||||||
|
var r struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
json.Unmarshal(resp.Body.Bytes(), &r)
|
||||||
|
if r.Code != 40003 && r.Code != 40001 {
|
||||||
|
t.Errorf("Expected 40003 or 40001, got %d", r.Code)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Login Temp User
|
||||||
|
var tempToken string
|
||||||
|
var tempID string
|
||||||
|
|
||||||
|
t.Run("Login Temp User", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/auth/login", map[string]string{
|
||||||
|
"username": tempUser,
|
||||||
|
"password": tempPass,
|
||||||
|
}, "")
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
t.Logf("Login Response: %s", resp.Body.String())
|
||||||
|
|
||||||
|
var data LoginResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
tempToken = data.AccessToken
|
||||||
|
})
|
||||||
|
|
||||||
|
if tempToken == "" {
|
||||||
|
t.Fatal("Failed to get temp token, skipping remaining auth tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get User Info
|
||||||
|
t.Run("Get Temp User Info", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/auth/me", nil, tempToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data UserResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
tempID = data.ID
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. Update User Info
|
||||||
|
t.Run("Update Temp User Info", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/users/"+tempID, map[string]string{
|
||||||
|
"nickname": "Temp Nickname",
|
||||||
|
}, tempToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. Logout
|
||||||
|
t.Run("Logout Temp User", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/auth/logout", map[string]interface{}{}, tempToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 6. Verify Token Invalid after Logout (Optional, depends on implementation)
|
||||||
|
// If logout blacklist is implemented, this should fail with 401
|
||||||
|
}
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensureUsers ensures that the test users exist and tokens are set
|
||||||
|
func ensureUsers(t *testing.T) {
|
||||||
|
if AdminToken != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
adminUser := "admin_test"
|
||||||
|
adminPass := "password123"
|
||||||
|
adminEmail := "admin@test.com"
|
||||||
|
|
||||||
|
// Try to login first (in case DB persists but memory cleared - unlikely with TestMain cleanup)
|
||||||
|
// Or just register and ignore "already exists" error
|
||||||
|
|
||||||
|
// Register Admin
|
||||||
|
registerResp := doRequest(t, "POST", "/api/auth/register", map[string]string{
|
||||||
|
"username": adminUser,
|
||||||
|
"password": adminPass,
|
||||||
|
"email": adminEmail,
|
||||||
|
}, "")
|
||||||
|
// If 200 or 400 (already exists), proceed to login
|
||||||
|
if registerResp.Code != 200 {
|
||||||
|
// Verify if it's because user already exists
|
||||||
|
var resp struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(registerResp.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
if resp.Code != 40003 && resp.Code != 40001 {
|
||||||
|
t.Errorf("Expected Vigo code 40003 or 40001, got %d", resp.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login Admin
|
||||||
|
loginResp := doRequest(t, "POST", "/api/auth/login", map[string]string{
|
||||||
|
"username": adminUser,
|
||||||
|
"password": adminPass,
|
||||||
|
}, "")
|
||||||
|
assertStatus(t, loginResp, 200)
|
||||||
|
|
||||||
|
var loginData LoginResp
|
||||||
|
decodeResponse(t, loginResp, &loginData)
|
||||||
|
AdminToken = loginData.AccessToken
|
||||||
|
AdminID = loginData.User.ID
|
||||||
|
|
||||||
|
// Verify me endpoint works (optional, but good for sanity)
|
||||||
|
// meResp := doRequest(t, "GET", "/api/auth/me", nil, AdminToken)
|
||||||
|
// assertStatus(t, meResp, 200)
|
||||||
|
|
||||||
|
// User1
|
||||||
|
user1Name := "user1_test"
|
||||||
|
user1Pass := "password123"
|
||||||
|
user1Email := "user1@test.com"
|
||||||
|
|
||||||
|
doRequest(t, "POST", "/api/auth/register", map[string]string{
|
||||||
|
"username": user1Name,
|
||||||
|
"password": user1Pass,
|
||||||
|
"email": user1Email,
|
||||||
|
}, "")
|
||||||
|
|
||||||
|
loginResp1 := doRequest(t, "POST", "/api/auth/login", map[string]string{
|
||||||
|
"username": user1Name,
|
||||||
|
"password": user1Pass,
|
||||||
|
}, "")
|
||||||
|
assertStatus(t, loginResp1, 200)
|
||||||
|
var loginData1 LoginResp
|
||||||
|
decodeResponse(t, loginResp1, &loginData1)
|
||||||
|
User1Token = loginData1.AccessToken
|
||||||
|
User1ID = loginData1.User.ID
|
||||||
|
|
||||||
|
// User2
|
||||||
|
user2Name := "user2_test"
|
||||||
|
user2Pass := "password123"
|
||||||
|
user2Email := "user2@test.com"
|
||||||
|
|
||||||
|
doRequest(t, "POST", "/api/auth/register", map[string]string{
|
||||||
|
"username": user2Name,
|
||||||
|
"password": user2Pass,
|
||||||
|
"email": user2Email,
|
||||||
|
}, "")
|
||||||
|
|
||||||
|
loginResp2 := doRequest(t, "POST", "/api/auth/login", map[string]string{
|
||||||
|
"username": user2Name,
|
||||||
|
"password": user2Pass,
|
||||||
|
}, "")
|
||||||
|
assertStatus(t, loginResp2, 200)
|
||||||
|
var loginData2 LoginResp
|
||||||
|
decodeResponse(t, loginResp2, &loginData2)
|
||||||
|
User2Token = loginData2.AccessToken
|
||||||
|
User2ID = loginData2.User.ID
|
||||||
|
}
|
||||||
@ -0,0 +1,169 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/veypi/vbase"
|
||||||
|
"github.com/veypi/vbase/cfg"
|
||||||
|
"github.com/veypi/vbase/models"
|
||||||
|
"github.com/veypi/vigo/contrib/config"
|
||||||
|
"github.com/veypi/vigo/contrib/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TestDBFile = "test.db"
|
||||||
|
|
||||||
|
// Global variables to hold test data
|
||||||
|
var (
|
||||||
|
AdminToken string
|
||||||
|
User1Token string
|
||||||
|
User2Token string
|
||||||
|
AdminID string
|
||||||
|
User1ID string
|
||||||
|
User2ID string
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// Setup
|
||||||
|
setup()
|
||||||
|
|
||||||
|
// Run tests
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
// Teardown
|
||||||
|
teardown()
|
||||||
|
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() {
|
||||||
|
// Clean up previous run
|
||||||
|
os.Remove(TestDBFile)
|
||||||
|
|
||||||
|
// Configure for testing
|
||||||
|
cfg.Config.DB = config.Database{
|
||||||
|
Type: "sqlite",
|
||||||
|
DSN: TestDBFile,
|
||||||
|
}
|
||||||
|
cfg.Config.Redis = config.Redis{
|
||||||
|
Addr: "memory",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize DB connection
|
||||||
|
// Force re-initialization if necessary, but cfg.Config.DB.Client calls might need a reset?
|
||||||
|
// Assuming vigo/contrib/config handles this or we need to manually trigger it.
|
||||||
|
// Looking at cfg.go: var DB = Config.DB.Client
|
||||||
|
// Since DB is a variable, it might have been initialized with default values.
|
||||||
|
// We might need to rely on the fact that Config.DB.Client() creates a new connection based on current Config.DB values.
|
||||||
|
// But wait, cfg.DB is a variable initialized at package level.
|
||||||
|
// If the app uses cfg.DB directly, it might be stale.
|
||||||
|
// However, looking at main.go, it uses `models.Migrate()`.
|
||||||
|
// models/init.go probably uses cfg.DB.
|
||||||
|
|
||||||
|
// Important: Initialize the application components
|
||||||
|
models.Migrate()
|
||||||
|
event.Start()
|
||||||
|
|
||||||
|
// Set router
|
||||||
|
// vbase.Router is already initialized in init.go
|
||||||
|
}
|
||||||
|
|
||||||
|
func teardown() {
|
||||||
|
os.Remove(TestDBFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
func doRequest(t *testing.T, method, path string, body interface{}, token string) *httptest.ResponseRecorder {
|
||||||
|
var bodyReader io.Reader
|
||||||
|
if body != nil {
|
||||||
|
jsonBody, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to marshal body: %v", err)
|
||||||
|
}
|
||||||
|
bodyReader = bytes.NewReader(jsonBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, path, bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Create a vigo context to wrap the request
|
||||||
|
// However, since we are testing the router, we should just serve HTTP
|
||||||
|
vbase.Router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertStatus(t *testing.T, w *httptest.ResponseRecorder, expectedCode int) {
|
||||||
|
if w.Code != expectedCode {
|
||||||
|
// Vigo might return 200 OK but with a JSON error code in body
|
||||||
|
// Check body for error details
|
||||||
|
t.Errorf("Expected HTTP status %d, got %d. Body: %s", expectedCode, w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertVigoCode checks the 'code' field in JSON response
|
||||||
|
func assertVigoCode(t *testing.T, w *httptest.ResponseRecorder, expectedCode int) {
|
||||||
|
var resp struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("Failed to unmarshal response: %v. Body: %s", err, w.Body.String())
|
||||||
|
}
|
||||||
|
if resp.Code != expectedCode {
|
||||||
|
t.Errorf("Expected Vigo code %d, got %d. Body: %s", expectedCode, resp.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeResponse decodes the JSON response into v
|
||||||
|
func decodeResponse(t *testing.T, w *httptest.ResponseRecorder, v interface{}) {
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), v); err != nil {
|
||||||
|
t.Fatalf("Failed to unmarshal response: %v. Body: %s", err, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common structs for responses
|
||||||
|
type BaseResp struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResp struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
} `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserResp struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
RoleCode string `json:"role_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrgResp struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoneAuth(t *testing.T) {
|
||||||
|
endpoints := []struct {
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
body interface{}
|
||||||
|
}{
|
||||||
|
{"GET", "/api/auth/me", nil},
|
||||||
|
{"POST", "/api/auth/logout", map[string]interface{}{}},
|
||||||
|
{"GET", "/api/users", nil},
|
||||||
|
{"POST", "/api/users", map[string]interface{}{}},
|
||||||
|
{"GET", "/api/orgs", nil},
|
||||||
|
{"POST", "/api/orgs", map[string]interface{}{}},
|
||||||
|
{"GET", "/api/roles", nil},
|
||||||
|
{"POST", "/api/roles", map[string]interface{}{}},
|
||||||
|
{"GET", "/api/settings", nil},
|
||||||
|
{"GET", "/api/oauth/clients", nil},
|
||||||
|
{"GET", "/api/oauth/providers", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep := range endpoints {
|
||||||
|
t.Run(ep.method+" "+ep.path, func(t *testing.T) {
|
||||||
|
w := doRequest(t, ep.method, ep.path, ep.body, "")
|
||||||
|
|
||||||
|
// Expect 401 Unauthorized
|
||||||
|
// Vigo might return 200 with code 40100 in JSON
|
||||||
|
if w.Code == http.StatusUnauthorized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check JSON body for code 40100
|
||||||
|
assertVigoCode(t, w, 40100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrgLoadMiddleware(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var orgID string
|
||||||
|
|
||||||
|
// 1. User1 Creates Org (Owner)
|
||||||
|
t.Run("User1 Creates Org", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
||||||
|
"code": "test_org_load_mw",
|
||||||
|
"name": "Test Org Load Middleware",
|
||||||
|
"description": "Created by User1 for Middleware Test",
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
orgID = data.ID
|
||||||
|
})
|
||||||
|
|
||||||
|
if orgID == "" {
|
||||||
|
t.Fatal("Failed to create org, skipping remaining tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. User1 Get Org Details (Success)
|
||||||
|
t.Run("User1 Get Org Details", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data OrgResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
if data.Name != "Test Org Load Middleware" {
|
||||||
|
t.Errorf("Expected name 'Test Org Load Middleware', got '%s'", data.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. User1 Update Org (Success)
|
||||||
|
t.Run("User1 Update Org", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/orgs/"+orgID, map[string]string{
|
||||||
|
"name": "Updated Org Middleware",
|
||||||
|
}, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. User2 Get Org Details (Fail - 403 Forbidden)
|
||||||
|
t.Run("User2 Get Org Details (Fail)", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User2Token)
|
||||||
|
|
||||||
|
// Expect 403 or 404 depending on implementation of LoadOrg
|
||||||
|
// Usually 403 if authenticated but not authorized
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("Expected error code (403/404), got 200")
|
||||||
|
} else {
|
||||||
|
// Optional: check specific error code in body
|
||||||
|
var errResp BaseResp
|
||||||
|
decodeResponse(t, resp, &errResp)
|
||||||
|
// e.g. 40300 or similar
|
||||||
|
if errResp.Code < 40000 {
|
||||||
|
t.Logf("Got error code: %d, msg: %s", errResp.Code, errResp.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. User1 adds User2 as Member
|
||||||
|
t.Run("User1 adds User2 as Member", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
|
||||||
|
"user_id": User2ID,
|
||||||
|
"role": "member",
|
||||||
|
}, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 6. User2 Get Org Details (Success - Now Member)
|
||||||
|
t.Run("User2 Get Org Details (Success)", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User2Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data OrgResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
if data.Name != "Updated Org Middleware" {
|
||||||
|
t.Errorf("Expected name 'Updated Org Middleware', got '%s'", data.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrgPermission(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// User1 will be the Org Creator (Owner)
|
||||||
|
// User2 will be the Outsider -> Member
|
||||||
|
|
||||||
|
var orgID string
|
||||||
|
|
||||||
|
// 1. User1 Creates Org
|
||||||
|
t.Run("User1 Creates Org", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
||||||
|
"code": "test_org_1",
|
||||||
|
"name": "Test Org 1",
|
||||||
|
"description": "Created by User1",
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
// If org code already exists (from previous run), we might get 400
|
||||||
|
// But let's assume clean run or handle unique code
|
||||||
|
if resp.Code == 400 {
|
||||||
|
// Try to get the org if it exists, or just use a unique code
|
||||||
|
// For simplicity in TestMain environment, we can use a fixed code
|
||||||
|
// If it fails, we might need to query it.
|
||||||
|
// Let's just assert 200 for now as we clean DB.
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
orgID = data.ID
|
||||||
|
})
|
||||||
|
|
||||||
|
if orgID == "" {
|
||||||
|
t.Fatal("Failed to create org, skipping remaining org tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. User2 tries to update Org (Should Fail - Outsider)
|
||||||
|
t.Run("User2 (Outsider) updates Org", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/orgs/"+orgID, map[string]string{
|
||||||
|
"name": "Hacked By User2",
|
||||||
|
}, User2Token)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
// Good
|
||||||
|
} else {
|
||||||
|
var errResp BaseResp
|
||||||
|
decodeResponse(t, resp, &errResp)
|
||||||
|
if errResp.Code < 40000 {
|
||||||
|
t.Errorf("Expected error code, got %d. Msg: %s", errResp.Code, errResp.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. User1 adds User2 as Member
|
||||||
|
t.Run("User1 adds User2 as Member", func(t *testing.T) {
|
||||||
|
// Endpoint: POST /api/orgs/:id/users
|
||||||
|
// Body: { user_id: "...", role_code: "member" }
|
||||||
|
resp := doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
|
||||||
|
"user_id": User2ID,
|
||||||
|
"role": "member",
|
||||||
|
}, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. User2 (Member) tries to update Org (Should Fail - Member cannot update org info)
|
||||||
|
t.Run("User2 (Member) updates Org", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/orgs/"+orgID, map[string]string{
|
||||||
|
"name": "Hacked By Member",
|
||||||
|
}, User2Token)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
// Good
|
||||||
|
} else {
|
||||||
|
var errResp BaseResp
|
||||||
|
decodeResponse(t, resp, &errResp)
|
||||||
|
if errResp.Code < 40000 {
|
||||||
|
t.Errorf("Expected error code, got %d. Msg: %s", errResp.Code, errResp.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. User1 (Owner) updates Org (Should Success)
|
||||||
|
t.Run("User1 (Owner) updates Org", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/orgs/"+orgID, map[string]string{
|
||||||
|
"name": "Updated By User1",
|
||||||
|
}, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data OrgResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
if data.Name != "Updated By User1" {
|
||||||
|
t.Errorf("Expected name 'Updated By User1', got '%s'", data.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResourcePermission(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// Case 1: Admin modifies User1 (Should Success)
|
||||||
|
t.Run("Admin modifies User1", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/users/"+User1ID, map[string]string{
|
||||||
|
"nickname": "Edited By Admin",
|
||||||
|
}, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data UserResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
if data.Nickname != "Edited By Admin" {
|
||||||
|
t.Errorf("Expected nickname 'Edited By Admin', got '%s'", data.Nickname)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Case 2: User1 modifies User1 (Should Success)
|
||||||
|
t.Run("User1 modifies User1", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/users/"+User1ID, map[string]string{
|
||||||
|
"nickname": "Edited By Self",
|
||||||
|
}, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data UserResp
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
if data.Nickname != "Edited By Self" {
|
||||||
|
t.Errorf("Expected nickname 'Edited By Self', got '%s'", data.Nickname)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Case 3: User1 modifies User2 (Should Fail 403/404)
|
||||||
|
t.Run("User1 modifies User2", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/users/"+User2ID, map[string]string{
|
||||||
|
"nickname": "Hacked By User1",
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
// Expecting 403 Forbidden or 404 NotFound
|
||||||
|
if resp.Code != 200 {
|
||||||
|
// Good
|
||||||
|
} else {
|
||||||
|
// Check Vigo code
|
||||||
|
var errResp BaseResp
|
||||||
|
decodeResponse(t, resp, &errResp)
|
||||||
|
// Common Forbidden/NotFound codes: 40300, 40400, etc.
|
||||||
|
// Or maybe 40100 Unauthorized
|
||||||
|
if errResp.Code < 40000 {
|
||||||
|
t.Errorf("Expected error code, got %d. Msg: %s", errResp.Code, errResp.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Case 4: User1 modifies Admin (Should Fail 403/404)
|
||||||
|
t.Run("User1 modifies Admin", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/users/"+AdminID, map[string]string{
|
||||||
|
"nickname": "Hacked By User1",
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
// Good
|
||||||
|
} else {
|
||||||
|
var errResp BaseResp
|
||||||
|
decodeResponse(t, resp, &errResp)
|
||||||
|
if errResp.Code < 40000 {
|
||||||
|
t.Errorf("Expected error code, got %d. Msg: %s", errResp.Code, errResp.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue