147 lines
4.9 KiB
Go
147 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// 🧪 MASTER 16 TESTSUITE: High-Concurrency Microservice API
|
|
|
|
func assert(condition bool, msg string) {
|
|
if !condition {
|
|
panic("❌ Test fehlgeschlagen: " + msg)
|
|
}
|
|
}
|
|
|
|
func assertEquals[T comparable](actual, expected T, msg string) {
|
|
if actual != expected {
|
|
panic(fmt.Sprintf("❌ Test fehlgeschlagen: %s (Erwartet: '%v', Erhalten: '%v')", msg, expected, actual))
|
|
}
|
|
}
|
|
|
|
// TEST: TestShortenAndResolve - Prüft grundlegende URL-Kürzung und Auflösung
|
|
func TestShortenAndResolve() {
|
|
fmt.Println("=== RUN TestShortenAndResolve")
|
|
srv := NewMicroserviceServer(100)
|
|
srv.StartWorkerPool(2)
|
|
defer srv.StopWorkerPool()
|
|
|
|
// 1. URL kürzen
|
|
rec, err := srv.ShortenURL("https://cloud.google.com/go")
|
|
assert(err == nil, "ShortenURL sollte bei valider HTTPS-URL erfolgreich sein")
|
|
assert(len(rec.ShortCode) >= 6, "ShortCode muss mindestens 6 Zeichen lang sein")
|
|
assertEquals(rec.OriginalURL, "https://cloud.google.com/go", "OriginalURL muss übereinstimmen")
|
|
|
|
// 2. Ungültige URL prüfen
|
|
_, errInvalid := srv.ShortenURL("ftp://invalid-protocol")
|
|
assert(errInvalid != nil, "Ungültiges URL-Protokoll muss abgewiesen werden")
|
|
|
|
// 3. URL auflösen
|
|
orig, errResolve := srv.ResolveURL(rec.ShortCode, "192.168.1.100", "Mozilla/5.0")
|
|
assert(errResolve == nil, "ResolveURL muss den ShortCode auflösen")
|
|
assertEquals(orig, "https://cloud.google.com/go", "Aufgelöste URL muss mit Original übereinstimmen")
|
|
fmt.Println("--- PASS: TestShortenAndResolve")
|
|
}
|
|
|
|
// TEST: TestWorkerPoolAsyncAnalytics - Prüft asynchrone Klick-Verarbeitung
|
|
func TestWorkerPoolAsyncAnalytics() {
|
|
fmt.Println("=== RUN TestWorkerPoolAsyncAnalytics")
|
|
srv := NewMicroserviceServer(1000)
|
|
srv.StartWorkerPool(4)
|
|
|
|
rec, _ := srv.ShortenURL("https://golang.org/pkg")
|
|
|
|
// 5 Klicks von 3 verschiedenen IPs simulieren
|
|
ips := []string{"10.0.0.1", "10.0.0.2", "10.0.0.1", "10.0.0.3", "10.0.0.2"}
|
|
for _, ip := range ips {
|
|
_, _ = srv.ResolveURL(rec.ShortCode, ip, "Go-Test-Agent")
|
|
}
|
|
|
|
// Workerpool stoppen -> Wartet bis alle Events in der Queue verarbeitet sind!
|
|
srv.StopWorkerPool()
|
|
|
|
stats, err := srv.GetStats(rec.ShortCode)
|
|
assert(err == nil, "GetStats sollte für bestehenden Code Daten liefern")
|
|
assertEquals(stats.TotalClicks, int64(5), "TotalClicks muss 5 sein")
|
|
assertEquals(stats.UniqueIPs, 3, "UniqueIPs muss exakt 3 sein")
|
|
fmt.Println("--- PASS: TestWorkerPoolAsyncAnalytics")
|
|
}
|
|
|
|
// TEST: TestConcurrentClickStressTest - 100 gleichzeitige Klicks
|
|
func TestConcurrentClickStressTest() {
|
|
fmt.Println("=== RUN TestConcurrentClickStressTest")
|
|
srv := NewMicroserviceServer(5000)
|
|
srv.StartWorkerPool(8)
|
|
|
|
rec, _ := srv.ShortenURL("https://kubernetes.io")
|
|
var wg sync.WaitGroup
|
|
numClicks := 100
|
|
|
|
for i := 0; i < numClicks; i++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
ip := fmt.Sprintf("172.16.0.%d", id%10) // 10 distinct IPs
|
|
_, _ = srv.ResolveURL(rec.ShortCode, ip, "StressTestBot")
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
srv.StopWorkerPool()
|
|
|
|
stats, _ := srv.GetStats(rec.ShortCode)
|
|
assertEquals(stats.TotalClicks, int64(numClicks), "Alle 100 parallelen Klicks müssen registriert sein")
|
|
assertEquals(stats.UniqueIPs, 10, "Genau 10 Unique IPs sollten erfasst sein")
|
|
fmt.Println("--- PASS: TestConcurrentClickStressTest")
|
|
}
|
|
|
|
// TEST: TestHTTPRouterEndpoints - Prüft REST HTTP Integration
|
|
func TestHTTPRouterEndpoints() {
|
|
fmt.Println("=== RUN TestHTTPRouterEndpoints")
|
|
srv := NewMicroserviceServer(100)
|
|
srv.StartWorkerPool(2)
|
|
defer srv.StopWorkerPool()
|
|
|
|
router := SetupMicroserviceRouter(srv)
|
|
|
|
// 1. POST /api/shorten
|
|
payload := `{"url":"https://github.com/golang/go"}`
|
|
reqPost := httptest.NewRequest(http.MethodPost, "/api/shorten", bytes.NewBufferString(payload))
|
|
recPost := httptest.NewRecorder()
|
|
router.ServeHTTP(recPost, reqPost)
|
|
|
|
assertEquals(recPost.Code, http.StatusCreated, "POST /api/shorten muss 201 Created liefern")
|
|
|
|
var created URLRecord
|
|
json.Unmarshal(recPost.Body.Bytes(), &created)
|
|
assert(created.ShortCode != "", "ShortCode muss generiert worden sein")
|
|
|
|
// 2. GET /r/{short_code}
|
|
reqGet := httptest.NewRequest(http.MethodGet, "/r/"+created.ShortCode, nil)
|
|
recGet := httptest.NewRecorder()
|
|
router.ServeHTTP(recGet, reqGet)
|
|
|
|
assertEquals(recGet.Code, http.StatusOK, "GET /r/{code} muss 200 OK liefern")
|
|
|
|
// 3. GET /api/metrics
|
|
reqMetrics := httptest.NewRequest(http.MethodGet, "/api/metrics", nil)
|
|
recMetrics := httptest.NewRecorder()
|
|
router.ServeHTTP(recMetrics, reqMetrics)
|
|
|
|
assertEquals(recMetrics.Code, http.StatusOK, "GET /api/metrics muss 200 OK liefern")
|
|
fmt.Println("--- PASS: TestHTTPRouterEndpoints")
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println("🧪 Starte Master 16 Abschlussprojekt Testsuite...")
|
|
TestShortenAndResolve()
|
|
TestWorkerPoolAsyncAnalytics()
|
|
TestConcurrentClickStressTest()
|
|
TestHTTPRouterEndpoints()
|
|
fmt.Println("\n🏆 GRATULATION! Alle Tests im Master-Abschlussprojekt 16 erfolgreich bestanden!")
|
|
}
|