mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-24 08:33:41 +01:00
Exposes the server's configured client mode in a form that mhf-outpost and other launcher tools can consume to warn users when their local game version does not match what the server expects. Returns clientMode (raw, e.g. "G9.1") and manifestId (normalized for mhf-outpost: lowercase, dots stripped, e.g. "g91"). No auth required.
446 lines
10 KiB
Go
446 lines
10 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
cfg "erupe-ce/config"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func TestVersionEndpoint(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
c.ClientMode = "ZZ"
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/version", nil)
|
|
rec := httptest.NewRecorder()
|
|
server.Version(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
|
|
var resp VersionResponse
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if resp.ClientMode != "ZZ" {
|
|
t.Errorf("ClientMode = %q, want ZZ", resp.ClientMode)
|
|
}
|
|
if resp.Name != "Erupe-CE" {
|
|
t.Errorf("Name = %q, want Erupe-CE", resp.Name)
|
|
}
|
|
}
|
|
|
|
func TestServerInfoEndpoint(t *testing.T) {
|
|
tests := []struct {
|
|
clientMode string
|
|
wantID string
|
|
}{
|
|
{"ZZ", "zz"},
|
|
{"GG", "gg"},
|
|
{"G10.1", "g101"},
|
|
{"G9.1", "g91"},
|
|
{"FW.5", "fw5"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.clientMode, func(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
c.ClientMode = tt.clientMode
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/v2/server/info", nil)
|
|
rec := httptest.NewRecorder()
|
|
server.ServerInfo(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
if ct := rec.Header().Get("Content-Type"); ct != "application/json" {
|
|
t.Errorf("Content-Type = %q, want application/json", ct)
|
|
}
|
|
|
|
var resp ServerInfoResponse
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if resp.ClientMode != tt.clientMode {
|
|
t.Errorf("ClientMode = %q, want %q", resp.ClientMode, tt.clientMode)
|
|
}
|
|
if resp.ManifestID != tt.wantID {
|
|
t.Errorf("ManifestID = %q, want %q", resp.ManifestID, tt.wantID)
|
|
}
|
|
if resp.Name != "Erupe-CE" {
|
|
t.Errorf("Name = %q, want Erupe-CE", resp.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLandingPageEndpoint_Enabled(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
c.API.LandingPage = cfg.LandingPage{
|
|
Enabled: true,
|
|
Title: "Test Server",
|
|
Content: "<p>Welcome</p>",
|
|
}
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
rec := httptest.NewRecorder()
|
|
server.LandingPage(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
if ct := rec.Header().Get("Content-Type"); ct != "text/html; charset=utf-8" {
|
|
t.Errorf("Content-Type = %q", ct)
|
|
}
|
|
}
|
|
|
|
func TestLandingPageEndpoint_Disabled(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
c.API.LandingPage = cfg.LandingPage{Enabled: false}
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
rec := httptest.NewRecorder()
|
|
server.LandingPage(rec, req)
|
|
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Errorf("status = %d, want 404", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestLoginEndpoint_Success(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
hash, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.MinCost)
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
userRepo: &mockAPIUserRepo{
|
|
credentialsID: 1,
|
|
credentialsPassword: string(hash),
|
|
credentialsRights: 30,
|
|
lastLogin: time.Now(),
|
|
returnExpiry: time.Now().Add(time.Hour * 24 * 30),
|
|
},
|
|
sessionRepo: &mockAPISessionRepo{
|
|
createTokenID: 42,
|
|
},
|
|
charRepo: &mockAPICharacterRepo{
|
|
characters: []Character{
|
|
{ID: 1, Name: "TestHunter", HR: 100},
|
|
},
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"username": "testuser",
|
|
"password": "password123",
|
|
})
|
|
req := httptest.NewRequest("POST", "/login", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.Login(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
|
|
var resp AuthData
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if resp.User.TokenID != 42 {
|
|
t.Errorf("TokenID = %d, want 42", resp.User.TokenID)
|
|
}
|
|
if len(resp.Characters) != 1 {
|
|
t.Errorf("Characters count = %d, want 1", len(resp.Characters))
|
|
}
|
|
}
|
|
|
|
func TestLoginEndpoint_UsernameNotFound(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
userRepo: &mockAPIUserRepo{
|
|
credentialsErr: sql.ErrNoRows,
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"username": "nonexistent",
|
|
"password": "password123",
|
|
})
|
|
req := httptest.NewRequest("POST", "/login", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.Login(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("status = %d, want 400", rec.Code)
|
|
}
|
|
var errResp ErrorResponse
|
|
if err := json.NewDecoder(rec.Body).Decode(&errResp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if errResp.Error != "invalid_username" {
|
|
t.Errorf("error = %q, want invalid_username", errResp.Error)
|
|
}
|
|
}
|
|
|
|
func TestLoginEndpoint_WrongPassword(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
hash, _ := bcrypt.GenerateFromPassword([]byte("correct"), bcrypt.MinCost)
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
userRepo: &mockAPIUserRepo{
|
|
credentialsID: 1,
|
|
credentialsPassword: string(hash),
|
|
lastLogin: time.Now(),
|
|
returnExpiry: time.Now().Add(time.Hour * 24 * 30),
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"username": "testuser",
|
|
"password": "wrongpassword",
|
|
})
|
|
req := httptest.NewRequest("POST", "/login", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.Login(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("status = %d, want 400", rec.Code)
|
|
}
|
|
var errResp ErrorResponse
|
|
if err := json.NewDecoder(rec.Body).Decode(&errResp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if errResp.Error != "invalid_password" {
|
|
t.Errorf("error = %q, want invalid_password", errResp.Error)
|
|
}
|
|
}
|
|
|
|
func TestRegisterEndpoint_Success(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
userRepo: &mockAPIUserRepo{
|
|
registerID: 1,
|
|
registerRights: 30,
|
|
lastLogin: time.Now(),
|
|
returnExpiry: time.Now().Add(time.Hour * 24 * 30),
|
|
},
|
|
sessionRepo: &mockAPISessionRepo{
|
|
createTokenID: 10,
|
|
},
|
|
charRepo: &mockAPICharacterRepo{},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"username": "newuser",
|
|
"password": "password123",
|
|
})
|
|
req := httptest.NewRequest("POST", "/register", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.Register(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
|
|
var resp AuthData
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if resp.User.Rights != 30 {
|
|
t.Errorf("Rights = %d, want 30", resp.User.Rights)
|
|
}
|
|
}
|
|
|
|
func TestCreateCharacterEndpoint_Success(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
sessionRepo: &mockAPISessionRepo{
|
|
userID: 1,
|
|
},
|
|
charRepo: &mockAPICharacterRepo{
|
|
newCharacter: Character{ID: 5, Name: "NewChar"},
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"token": "valid-token",
|
|
})
|
|
req := httptest.NewRequest("POST", "/character/create", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.CreateCharacter(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateCharacterEndpoint_InvalidToken(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
sessionRepo: &mockAPISessionRepo{
|
|
userIDErr: sql.ErrNoRows,
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]string{
|
|
"token": "invalid",
|
|
})
|
|
req := httptest.NewRequest("POST", "/character/create", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.CreateCharacter(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Errorf("status = %d, want 401", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteCharacterEndpoint_NewChar(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
sessionRepo: &mockAPISessionRepo{
|
|
userID: 1,
|
|
},
|
|
charRepo: &mockAPICharacterRepo{
|
|
isNewResult: true,
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]interface{}{
|
|
"token": "valid-token",
|
|
"charId": 5,
|
|
})
|
|
req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.DeleteCharacter(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteCharacterEndpoint_FinalizedChar(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
sessionRepo: &mockAPISessionRepo{
|
|
userID: 1,
|
|
},
|
|
charRepo: &mockAPICharacterRepo{
|
|
isNewResult: false,
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]interface{}{
|
|
"token": "valid-token",
|
|
"charId": 5,
|
|
})
|
|
req := httptest.NewRequest("POST", "/character/delete", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.DeleteCharacter(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestExportSaveEndpoint_Success(t *testing.T) {
|
|
logger := NewTestLogger(t)
|
|
c := NewTestConfig()
|
|
|
|
server := &APIServer{
|
|
logger: logger,
|
|
erupeConfig: c,
|
|
sessionRepo: &mockAPISessionRepo{
|
|
userID: 1,
|
|
},
|
|
charRepo: &mockAPICharacterRepo{
|
|
exportResult: map[string]interface{}{
|
|
"name": "TestHunter",
|
|
"hr": 100,
|
|
},
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(map[string]interface{}{
|
|
"token": "valid-token",
|
|
"charId": 1,
|
|
})
|
|
req := httptest.NewRequest("POST", "/character/export", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
server.ExportSave(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want 200", rec.Code)
|
|
}
|
|
|
|
var resp ExportData
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("decode error: %v", err)
|
|
}
|
|
if resp.Character["name"] != "TestHunter" {
|
|
t.Errorf("character name = %v, want TestHunter", resp.Character["name"])
|
|
}
|
|
}
|