Files
a301_server/internal/download/service_test.go
tolelom 844a5b264b feat: 보안 수정 + Prometheus 메트릭 + 단위 테스트 추가
보안:
- Zip Bomb 방어 (io.LimitReader 100MB)
- Redis Del 에러 로깅 (auth, idempotency)
- 로그인 실패 로그에서 username 제거
- os.Remove 에러 로깅

모니터링:
- Prometheus 메트릭 미들웨어 + /metrics 엔드포인트
- http_requests_total, http_request_duration_seconds 등 4개 메트릭

테스트:
- download (11), chain (10), bossraid (20) = 41개 단위 테스트

기타:
- DB 모델 GORM 인덱스 태그 추가
- launcherHash 필드 + hashFileToHex() 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:37:42 +09:00

199 lines
4.7 KiB
Go

package download
import (
"archive/zip"
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"testing"
)
func TestHashFileToHex_KnownContent(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "testfile.bin")
content := []byte("hello world")
if err := os.WriteFile(path, content, 0644); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
got := hashFileToHex(path)
h := sha256.Sum256(content)
want := hex.EncodeToString(h[:])
if got != want {
t.Errorf("hashFileToHex = %q, want %q", got, want)
}
}
func TestHashFileToHex_EmptyFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "empty.bin")
if err := os.WriteFile(path, []byte{}, 0644); err != nil {
t.Fatalf("failed to write temp file: %v", err)
}
got := hashFileToHex(path)
h := sha256.Sum256([]byte{})
want := hex.EncodeToString(h[:])
if got != want {
t.Errorf("hashFileToHex (empty) = %q, want %q", got, want)
}
}
func TestHashFileToHex_NonExistentFile(t *testing.T) {
got := hashFileToHex("/nonexistent/path/file.bin")
if got != "" {
t.Errorf("hashFileToHex (nonexistent) = %q, want empty string", got)
}
}
// createTestZip creates a zip file at zipPath containing the given files.
// files is a map of filename -> content.
func createTestZip(t *testing.T, zipPath string, files map[string][]byte) {
t.Helper()
f, err := os.Create(zipPath)
if err != nil {
t.Fatalf("failed to create zip: %v", err)
}
defer f.Close()
w := zip.NewWriter(f)
for name, data := range files {
fw, err := w.Create(name)
if err != nil {
t.Fatalf("failed to create zip entry %s: %v", name, err)
}
if _, err := fw.Write(data); err != nil {
t.Fatalf("failed to write zip entry %s: %v", name, err)
}
}
if err := w.Close(); err != nil {
t.Fatalf("failed to close zip writer: %v", err)
}
}
func TestHashGameExeFromZip_WithA301Exe(t *testing.T) {
dir := t.TempDir()
zipPath := filepath.Join(dir, "game.zip")
exeContent := []byte("fake A301.exe binary content for testing")
createTestZip(t, zipPath, map[string][]byte{
"GameFolder/A301.exe": exeContent,
"GameFolder/readme.txt": []byte("readme"),
})
got := hashGameExeFromZip(zipPath)
h := sha256.Sum256(exeContent)
want := hex.EncodeToString(h[:])
if got != want {
t.Errorf("hashGameExeFromZip = %q, want %q", got, want)
}
}
func TestHashGameExeFromZip_CaseInsensitive(t *testing.T) {
dir := t.TempDir()
zipPath := filepath.Join(dir, "game.zip")
exeContent := []byte("case insensitive test")
createTestZip(t, zipPath, map[string][]byte{
"build/a301.EXE": exeContent,
})
got := hashGameExeFromZip(zipPath)
h := sha256.Sum256(exeContent)
want := hex.EncodeToString(h[:])
if got != want {
t.Errorf("hashGameExeFromZip (case insensitive) = %q, want %q", got, want)
}
}
func TestHashGameExeFromZip_NoA301Exe(t *testing.T) {
dir := t.TempDir()
zipPath := filepath.Join(dir, "game.zip")
createTestZip(t, zipPath, map[string][]byte{
"GameFolder/other.exe": []byte("not A301"),
"GameFolder/readme.txt": []byte("readme"),
})
got := hashGameExeFromZip(zipPath)
if got != "" {
t.Errorf("hashGameExeFromZip (no A301.exe) = %q, want empty string", got)
}
}
func TestHashGameExeFromZip_EmptyZip(t *testing.T) {
dir := t.TempDir()
zipPath := filepath.Join(dir, "empty.zip")
createTestZip(t, zipPath, map[string][]byte{})
got := hashGameExeFromZip(zipPath)
if got != "" {
t.Errorf("hashGameExeFromZip (empty zip) = %q, want empty string", got)
}
}
func TestHashGameExeFromZip_InvalidZip(t *testing.T) {
dir := t.TempDir()
zipPath := filepath.Join(dir, "notazip.zip")
if err := os.WriteFile(zipPath, []byte("this is not a zip file"), 0644); err != nil {
t.Fatalf("failed to write file: %v", err)
}
got := hashGameExeFromZip(zipPath)
if got != "" {
t.Errorf("hashGameExeFromZip (invalid zip) = %q, want empty string", got)
}
}
func TestVersionRegex(t *testing.T) {
tests := []struct {
input string
want string
}{
{"game_v1.2.3.zip", "v1.2.3"},
{"game_v2.0.zip", "v2.0"},
{"game_v10.20.30.zip", "v10.20.30"},
{"game.zip", ""},
{"noversion", ""},
}
for _, tt := range tests {
got := versionRe.FindString(tt.input)
if got != tt.want {
t.Errorf("versionRe.FindString(%q) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestGameFilePath(t *testing.T) {
s := NewService(nil, "/data/game")
got := s.GameFilePath()
// filepath.Join normalizes separators per OS
want := filepath.Join("/data/game", "game.zip")
if got != want {
t.Errorf("GameFilePath() = %q, want %q", got, want)
}
}
func TestLauncherFilePath(t *testing.T) {
s := NewService(nil, "/data/game")
got := s.LauncherFilePath()
want := filepath.Join("/data/game", "launcher.exe")
if got != want {
t.Errorf("LauncherFilePath() = %q, want %q", got, want)
}
}