feat: add Docker infrastructure, migrations, CI/CD client, session cleanup, tests
Docker & Deployment: - Add Dockerfile (multi-stage, alpine, non-root) - Add docker-compose.yml (caddy, backend, postgres, watchtower) - Add Caddyfile (TLS, file_server, reverse proxy) - Add .env.example Database: - Add migrations/001_init.sql (all tables + indexes) CI/CD: - Add cmd/ci-release/main.go (launcher binary upload tool) Session management: - Add internal/session/cleanup.go (background expired session cleanup) - Integrate cleanup worker into main.go Bug fixes: - Fix launcherLatest download URL to include version segment - Fix serveLauncherAsset path to match route pattern - Add Content-Type detection from file extension in CAS serveFile - Add empty-field validation in webLogin - Format string fix in ci-release (%d → %s for resp.Status) Tests: - Add internal/auth/auth_test.go (8 tests) - Add internal/cas/cas_test.go (7 tests) - Add internal/session/cleanup_test.go (1 test) - Add internal/api/api_test.go (5 tests) - All tests passing, go vet clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
117
internal/auth/auth_test.go
Normal file
117
internal/auth/auth_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateToken(t *testing.T) {
|
||||
tok := GenerateToken()
|
||||
if len(tok) != 32 {
|
||||
t.Errorf("expected 32-char token, got %d chars: %s", len(tok), tok)
|
||||
}
|
||||
// Must be hex.
|
||||
for _, c := range tok {
|
||||
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
||||
t.Errorf("token contains non-hex char: %c", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateToken_Uniqueness(t *testing.T) {
|
||||
// Two tokens should never collide.
|
||||
t1 := GenerateToken()
|
||||
t2 := GenerateToken()
|
||||
if t1 == t2 {
|
||||
t.Error("two generated tokens are identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateUUID(t *testing.T) {
|
||||
uuid := GenerateUUID()
|
||||
// Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (36 chars).
|
||||
if len(uuid) != 36 {
|
||||
t.Errorf("expected 36-char UUID, got %d: %s", len(uuid), uuid)
|
||||
}
|
||||
// Check dashes at correct positions.
|
||||
for _, pos := range []int{8, 13, 18, 23} {
|
||||
if uuid[pos] != '-' {
|
||||
t.Errorf("expected dash at position %d, got %c", pos, uuid[pos])
|
||||
}
|
||||
}
|
||||
// Version 4: char at position 14 should be '4'.
|
||||
if uuid[14] != '4' {
|
||||
t.Errorf("expected version 4 at position 14, got %c", uuid[14])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateUUID_Uniqueness(t *testing.T) {
|
||||
u1 := GenerateUUID()
|
||||
u2 := GenerateUUID()
|
||||
if u1 == u2 {
|
||||
t.Error("two generated UUIDs are identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashPassword(t *testing.T) {
|
||||
hash, err := HashPassword("testpassword")
|
||||
if err != nil {
|
||||
t.Fatalf("HashPassword failed: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(hash, "$2a$") {
|
||||
t.Errorf("expected bcrypt hash starting with $2a$, got: %s", hash[:4])
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPassword(t *testing.T) {
|
||||
hash, err := HashPassword("minecraft123")
|
||||
if err != nil {
|
||||
t.Fatalf("HashPassword failed: %v", err)
|
||||
}
|
||||
|
||||
if !VerifyPassword("minecraft123", hash) {
|
||||
t.Error("VerifyPassword returned false for correct password")
|
||||
}
|
||||
if VerifyPassword("wrongpassword", hash) {
|
||||
t.Error("VerifyPassword returned true for wrong password")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBcryptHash(t *testing.T) {
|
||||
tests := []struct {
|
||||
hash string
|
||||
want bool
|
||||
}{
|
||||
{"$2a$10$abcdefghijklmnopqrstuuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", true},
|
||||
{"$2b$10$abcdefghijklmnopqrstuuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", true},
|
||||
{"$2y$10$abcdefghijklmnopqrstuuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", true},
|
||||
{"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", false},
|
||||
{"", false},
|
||||
{"plaintext", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := IsBcryptHash(tt.hash)
|
||||
if got != tt.want {
|
||||
t.Errorf("IsBcryptHash(%q) = %v, want %v", tt.hash, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractBearer(t *testing.T) {
|
||||
tests := []struct {
|
||||
header string
|
||||
want string
|
||||
}{
|
||||
{"Bearer abc123", "abc123"},
|
||||
{"Bearer ", ""},
|
||||
{"abc123", ""},
|
||||
{"", ""},
|
||||
{"Basic abc123", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := ExtractBearer(tt.header)
|
||||
if got != tt.want {
|
||||
t.Errorf("ExtractBearer(%q) = %q, want %q", tt.header, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user