From 2fe0d73e946025bc22b3fcfb66e1c8a2fc4b2495 Mon Sep 17 00:00:00 2001 From: Nicolas Guillard Date: Tue, 11 Nov 2025 11:56:57 +0100 Subject: [PATCH 1/3] feature(translations): add translations --- .traefik.yml | 3 +- docker-compose.yml | 58 ++++--- go.mod | 2 +- queue-page.html => public/queue-page.html | 32 ++-- public/translations.json | 56 ++++++ queue-manager.go | 199 ++++++++++++++-------- readme.md | 76 ++++++--- 7 files changed, 292 insertions(+), 134 deletions(-) rename queue-page.html => public/queue-page.html (83%) create mode 100644 public/translations.json diff --git a/.traefik.yml b/.traefik.yml index e6c8b40..423871a 100644 --- a/.traefik.yml +++ b/.traefik.yml @@ -8,6 +8,7 @@ testData: enabled: true maxEntries: 100 sessionTime: 1m - queuePageFile: queue-page.html + queuePageFile: "/var/public/queue-page.html" + queueTranslationsFile: "/var/public/translations.json" useCookies: true cookieName: queue-manager-id \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 71780b4..c7114f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,6 @@ -# docker-compose.yml -version: "3.6" - services: traefik: - image: traefik:v3.3.4 - container_name: traefik + image: traefik:v3.6 command: - --log.level=INFO - --api @@ -21,34 +17,54 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - ./:/plugins-local/src/github.com/hhftechnology/traefik-queue-manager - labels: - - traefik.http.middlewares.queuemanager.plugin.queuemanager.enabled=true - - traefik.http.middlewares.queuemanager.plugin.queuemanager.queuePageFile=/plugins-local/src/github.com/hhftechnology/traefik-queue-manager/queue-page.html - - traefik.http.middlewares.queuemanager.plugin.queuemanager.maxEntries=5 - - traefik.http.middlewares.queuemanager.plugin.queuemanager.sessionTime=1m - - traefik.http.middlewares.queuemanager.plugin.queuemanager.purgeTime=5m - - traefik.http.middlewares.queuemanager.plugin.queuemanager.useCookies=true - - traefik.http.middlewares.queuemanager.plugin.queuemanager.cookieName=queue-manager-id - - traefik.http.middlewares.queuemanager.plugin.queuemanager.refreshInterval=30 - - traefik.http.middlewares.queuemanager.plugin.queuemanager.debug=true + - ./public:/var/public whoami: image: traefik/whoami - container_name: whoami-service depends_on: - traefik networks: - traefik-network - deploy: - replicas: 3 + volumes: + - ./:/plugins-local/src/github.com/hhftechnology/traefik-queue-manager labels: - traefik.enable=true - - traefik.http.routers.whoami.rule=Host(`whoami.local`) + - traefik.http.routers.whoami.rule=Host(`localhost`) - traefik.http.routers.whoami.entrypoints=web - traefik.http.routers.whoami.service=whoami-service - traefik.http.services.whoami-service.loadbalancer.server.port=80 - - traefik.http.routers.whoami.middlewares=queuemanager + - traefik.http.routers.whoami.middlewares=qm1 + - traefik.http.middlewares.qm1.plugin.queuemanager.enabled=true + - traefik.http.middlewares.qm1.plugin.queuemanager.maxEntries=1 + - traefik.http.middlewares.qm1.plugin.queuemanager.sessionTime=1m + - traefik.http.middlewares.qm1.plugin.queuemanager.purgeTime=5m + - traefik.http.middlewares.qm1.plugin.queuemanager.useCookies=true + - traefik.http.middlewares.qm1.plugin.queuemanager.cookieName=queue-manager-id + - traefik.http.middlewares.qm1.plugin.queuemanager.refreshInterval=30 + - traefik.http.middlewares.qm1.plugin.queuemanager.debug=true + + nginx: + image: nginx + depends_on: + - traefik + networks: + - traefik-network + labels: + - traefik.enable=true + - traefik.http.routers.service2.rule=Host(`127.0.0.1`) + - traefik.http.routers.service2.entrypoints=web + - traefik.http.routers.service2.service=nginx-service + - traefik.http.services.nginx-service.loadbalancer.server.port=80 + - traefik.http.routers.service2.middlewares=qm2 + - traefik.http.middlewares.qm2.plugin.queuemanager.enabled=true + - traefik.http.middlewares.qm2.plugin.queuemanager.maxEntries=2 + - traefik.http.middlewares.qm2.plugin.queuemanager.sessionTime=1m + - traefik.http.middlewares.qm2.plugin.queuemanager.purgeTime=5m + - traefik.http.middlewares.qm2.plugin.queuemanager.useCookies=true + - traefik.http.middlewares.qm2.plugin.queuemanager.cookieName=queue-manager-id + - traefik.http.middlewares.qm2.plugin.queuemanager.refreshInterval=30 + - traefik.http.middlewares.qm2.plugin.queuemanager.debug=true networks: traefik-network: - driver: bridge \ No newline at end of file + driver: bridge diff --git a/go.mod b/go.mod index 72a1dd1..9d0440c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/hhftechnology/traefik-queue-manager -go 1.19 +go 1.25 diff --git a/queue-page.html b/public/queue-page.html similarity index 83% rename from queue-page.html rename to public/queue-page.html index 53069c9..533b4e3 100644 --- a/queue-page.html +++ b/public/queue-page.html @@ -3,9 +3,9 @@ - Service Queue - Please Wait + [[.Translations.PageTitle]] - +

You're in the Queue

Our service is currently experiencing high demand. Please wait, and this page will refresh automatically.

[[.ProgressPercentage]]%
Your Position[[.Position]] / [[.QueueSize]]
Est. Wait Time~[[.EstimatedWaitTime]] min(s)

[[.Message]]

This page will refresh in [[.RefreshInterval]] seconds.

Debug Info:
[[.DebugInfo]]
` + fallbackHTML := `Service Queue

You're in the Queue

Our service is currently experiencing high demand. Please wait, and this page will refresh automatically.

[[.ProgressPercentage]]%
Your Position[[.Position]] / [[.QueueSize]]
Est. Wait Time~[[.EstimatedWaitTime]] min(s)

This page will refresh in [[.RefreshInterval]] seconds.

Debug Info:
[[.DebugInfo]]
` tmpl, err := template.New("FallbackQueuePage").Delims("[[", "]]").Parse(fallbackHTML) if err != nil { @@ -700,8 +760,11 @@ func (qm *QueueManager) serveFallbackTemplate(rw http.ResponseWriter, data Queue qm.logf("error", "Error executing fallback queue page template: %v", execErr) // Ultimate fallback to plain text if template execution fails rw.Header().Set("Content-Type", "text/plain; charset=utf-8") // Ensure plain text - fmt.Fprintf(rw, "You are %d of %d in queue. Estimated wait: ~%d min(s). Page will refresh in %d seconds.", - data.Position, data.QueueSize, data.EstimatedWaitTime, data.RefreshInterval) + _, printErr := fmt.Fprintf(rw, "You are %d of %d in queue. Estimated wait: ~%d min(s). Page will refresh in %d seconds.", + data.QueueData.Position, data.QueueData.QueueSize, data.QueueData.EstimatedWaitTime, data.QueueData.RefreshInterval) + if printErr != nil { + return + } } } @@ -823,7 +886,7 @@ func (qm *QueueManager) dequeueClientUnderLock(clientID string) { // Remove the element by slicing: qm.queue = append(qm.queue[:foundIndex], qm.queue[foundIndex+1:]...) // More explicit copy for clarity if needed, but above is idiomatic. copy(qm.queue[foundIndex:], qm.queue[foundIndex+1:]) // Shift elements left - qm.queue = qm.queue[:len(qm.queue)-1] // Truncate slice + qm.queue = qm.queue[:len(qm.queue)-1] // Truncate slice qm.logf("debug", "Client %s dequeued (under lock).", clientID) diff --git a/readme.md b/readme.md index 0a8bd3b..08d865c 100644 --- a/readme.md +++ b/readme.md @@ -80,20 +80,21 @@ experimental: ### Plugin Configuration Options -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `enabled` | boolean | `true` | Enable/disable the queue manager | -| `queuePageFile` | string | `queue-page.html` | Path to the queue page HTML template | -| `sessionTime` | duration | `60` | Duration for which a visitor session is valid | -| `purgeTime` | duration | `300` | How often expired sessions are purged from cache | -| `maxEntries` | int | `100` | Maximum number of concurrent users allowed | -| `httpResponseCode` | int | `429` | HTTP response code for queue page | -| `httpContentType` | string | `text/html; charset=utf-8` | Content type of queue page | -| `useCookies` | boolean | `true` | Use cookies for tracking; if false, uses IP+UserAgent hash | -| `cookieName` | string | `queue-manager-id` | Name of the cookie used for tracking | -| `cookieMaxAge` | int | `3600` | Max age of the cookie in seconds | -| `refreshInterval` | int | `30` | Refresh interval in seconds | -| `debug` | boolean | `false` | Enable debug logging | +| Option | Type | Default | Description | +|-------------------------|------|---------|------------------------------------------------------------| +| `enabled` | boolean | `true` | Enable/disable the queue manager | +| `queuePageFile` | string | `/var/public/queue-page.html` | Path to the queue page HTML template | +| `queueTranslationsFile` | string | `/var/public/translations.json` | Path to the queue json translations | +| `sessionTime` | duration | `60` | Duration for which a visitor session is valid | +| `purgeTime` | duration | `300` | How often expired sessions are purged from cache | +| `maxEntries` | int | `100` | Maximum number of concurrent users allowed | +| `httpResponseCode` | int | `429` | HTTP response code for queue page | +| `httpContentType` | string | `text/html; charset=utf-8` | Content type of queue page | +| `useCookies` | boolean | `true` | Use cookies for tracking; if false, uses IP+UserAgent hash | +| `cookieName` | string | `queue-manager-id` | Name of the cookie used for tracking | +| `cookieMaxAge` | int | `3600` | Max age of the cookie in seconds | +| `refreshInterval` | int | `30` | Refresh interval in seconds | +| `debug` | boolean | `false` | Enable debug logging | ### Example Configuration @@ -101,7 +102,8 @@ experimental: # Dynamic configuration with Docker provider labels: - traefik.http.middlewares.queuemanager.plugin.queuemanager.enabled=true - - traefik.http.middlewares.queuemanager.plugin.queuemanager.queuePageFile=/path/to/queue-page.html + - traefik.http.middlewares.queuemanager.plugin.queuemanager.queuePageFile=/var/public/queue-page.html + - traefik.http.middlewares.queuemanager.plugin.queuemanager.queueTranslationsFile=/var/public/translations.json - traefik.http.middlewares.queuemanager.plugin.queuemanager.maxEntries=500 - traefik.http.middlewares.queuemanager.plugin.queuemanager.sessionTime=5m - traefik.http.middlewares.queuemanager.plugin.queuemanager.useCookies=true @@ -112,12 +114,32 @@ labels: You can customize the queue page by modifying the HTML template. The template supports the following variables: -- `[[.Position]]` - Current position in queue -- `[[.QueueSize]]` - Total queue size -- `[[.EstimatedWaitTime]]` - Estimated wait time in minutes -- `[[.RefreshInterval]]` - Refresh interval in seconds -- `[[.ProgressPercentage]]` - Visual progress percentage -- `[[.Message]]` - Custom message +- `[[.QueueData.Position]]` - Current position in queue +- `[[.QueueData.QueueSize]]` - Total queue size +- `[[.QueueData.EstimatedWaitTime]]` - Estimated wait time in minutes +- `[[.QueueData.RefreshInterval]]` - Refresh interval in seconds +- `[[.QueueData.ProgressPercentage]]` - Visual progress percentage +- `[[.QueueData.Message]]` - Custom message + +## Translations + +Translations are loaded at boot from a json file. The key represent the ISO code with two letters (i.e.: en for en-US). + +```json +{ + "en": { + "pageTitle": "Service Queue - Please Wait", + "title": "You're in the Queue", + "introduction": "Our services are currently experiencing high demand. Your patience is appreciated. You will be automatically redirected when it's your turn.", + "yourPosition": "Your Position", + "eta": "Estimated Wait Time", + "refreshTime": "This page will automatically refresh in %d seconds", + "mins": "min(s)" + } +} +``` + +They are loaded in HTML using an upper first letter like this: `title` become `[[.Translations.Title]]` ## Example Usage with Docker Compose @@ -138,11 +160,10 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - ./:/plugins-local/src/github.com/hhftechnology/traefik-queue-manager + - ./public:/var/public labels: - - traefik.http.middlewares.queuemanager.plugin.queuemanager.enabled=true - - traefik.http.middlewares.queuemanager.plugin.queuemanager.queuePageFile=/plugins-local/src/github.com/hhftechnology/traefik-queue-manager/queue-page.html - - traefik.http.middlewares.queuemanager.plugin.queuemanager.maxEntries=5 - - traefik.http.middlewares.queuemanager.plugin.queuemanager.sessionTime=1m + - traefik.http.middlewares.queuemanager.plugin.queuemanager.queuePageFile=/plugins-local/src/github.com/hhftechnology/traefik-queue-manager/public/queue-page.html + - traefik.http.middlewares.queuemanager.plugin.queuemanager.queueTranslationsFile=/plugins-local/src/github.com/hhftechnology/traefik-queue-manager/public/translations.json myservice: image: traefik/whoami @@ -151,7 +172,10 @@ services: - traefik.enable=true - traefik.http.routers.myservice.rule=Host(`service.local`) - traefik.http.routers.myservice.entrypoints=web - - traefik.http.routers.myservice.middlewares=queuemanager + - traefik.http.routers.myservice.middlewares=qm + - traefik.http.middlewares.qm.plugin.queuemanager.enabled=true + - traefik.http.middlewares.qm.plugin.queuemanager.maxEntries=5 + - traefik.http.middlewares.qm.plugin.queuemanager.sessionTime=1m ``` ```yaml # This is an example of how to configure the Queue Manager middleware From d80b749862b0aeca1afda7f146f9f12c74091a45 Mon Sep 17 00:00:00 2001 From: Nicolas Guillard Date: Tue, 11 Nov 2025 12:12:07 +0100 Subject: [PATCH 2/3] feature(translations): add dynamic seconds placeholder --- public/queue-page.html | 2 +- public/translations.json | 12 +++--- queue-manager.go | 8 +++- queue-manager_test.go | 79 +++++++++++++++++++++++----------------- 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/public/queue-page.html b/public/queue-page.html index 533b4e3..9263a18 100644 --- a/public/queue-page.html +++ b/public/queue-page.html @@ -142,7 +142,7 @@
- [[ printf .Translations.RefreshTime .QueueData.RefreshInterval ]] + [[ printf .Translations.RefreshTime .QueueData.RefreshInterval | safeHtml ]]
diff --git a/public/translations.json b/public/translations.json index 92155b3..47d955b 100644 --- a/public/translations.json +++ b/public/translations.json @@ -5,7 +5,7 @@ "introduction": "Our services are currently experiencing high demand. Your patience is appreciated. You will be automatically redirected when it's your turn.", "yourPosition": "Your Position", "eta": "Estimated Wait Time", - "refreshTime": "This page will automatically refresh in %d seconds", + "refreshTime": "This page will automatically refresh in %d seconds", "mins": "min(s)" }, "fr": { @@ -14,7 +14,7 @@ "introduction": "Nos services rencontrent actuellement une forte demande. Merci pour votre patience. Vous serez automatiquement redirigé lorsque ce sera votre tour.", "yourPosition": "Votre position", "eta": "Temps d’attente estimé", - "refreshTime": "Cette page se rafraîchira automatiquement dans %d secondes", + "refreshTime": "Cette page se rafraîchira automatiquement dans %d secondes", "mins": "min(s)" }, "es": { @@ -23,7 +23,7 @@ "introduction": "Nuestros servicios están experimentando una alta demanda. Agradecemos tu paciencia. Serás redirigido automáticamente cuando sea tu turno.", "yourPosition": "Tu posición", "eta": "Tiempo de espera estimado", - "refreshTime": "Esta página se actualizará automáticamente en %d segundos", + "refreshTime": "Esta página se actualizará automáticamente en %d segundos", "mins": "min(s)" }, "de": { @@ -32,7 +32,7 @@ "introduction": "Unsere Dienste verzeichnen derzeit eine hohe Nachfrage. Vielen Dank für Ihre Geduld. Sie werden automatisch weitergeleitet, wenn Sie an der Reihe sind.", "yourPosition": "Ihre Position", "eta": "Geschätzte Wartezeit", - "refreshTime": "Diese Seite wird in %d Sekunden automatisch aktualisiert", + "refreshTime": "Diese Seite wird in %d Sekunden automatisch aktualisiert", "mins": "min(s)" }, "pt": { @@ -41,7 +41,7 @@ "introduction": "Nossos serviços estão com alta demanda no momento. Agradecemos sua paciência. Você será redirecionado automaticamente quando for a sua vez.", "yourPosition": "Sua posição", "eta": "Tempo de espera estimado", - "refreshTime": "Esta página será atualizada automaticamente em %d segundos", + "refreshTime": "Esta página será atualizada automaticamente em %d segundos", "mins": "min(s)" }, "it": { @@ -50,7 +50,7 @@ "introduction": "I nostri servizi stanno riscontrando un'alta domanda. Ti ringraziamo per la pazienza. Verrai reindirizzato automaticamente quando sarà il tuo turno.", "yourPosition": "La tua posizione", "eta": "Tempo di attesa stimato", - "refreshTime": "Questa pagina si aggiornerà automaticamente tra %d secondi", + "refreshTime": "Questa pagina si aggiornerà automaticamente tra %d secondi", "mins": "min(s)" } } \ No newline at end of file diff --git a/queue-manager.go b/queue-manager.go index 64a9894..1becb38 100644 --- a/queue-manager.go +++ b/queue-manager.go @@ -311,7 +311,11 @@ func (qm *QueueManager) loadTemplate() error { return fmt.Errorf("error reading template file '%s': %w", qm.config.QueuePageFile, err) } - newTpl, parseErr := template.New("QueuePage").Delims("[[", "]]").Parse(string(content)) + newTpl, parseErr := template.New("QueuePage").Delims("[[", "]]").Funcs(template.FuncMap{ + "safeHtml": func(s string) template.HTML { + return template.HTML(s) + }, + }).Parse(string(content)) if parseErr != nil { qm.tpl = nil return fmt.Errorf("error parsing template '%s': %w", qm.config.QueuePageFile, parseErr) @@ -744,7 +748,7 @@ func (qm *QueueManager) prepareQueuePageData(positionInQueue int) QueuePageData // serveFallbackTemplate provides a basic, hardcoded HTML queue page. func (qm *QueueManager) serveFallbackTemplate(rw http.ResponseWriter, data QueueTemplateData) { // Minified and slightly improved fallback HTML - fallbackHTML := `Service Queue

You're in the Queue

Our service is currently experiencing high demand. Please wait, and this page will refresh automatically.

[[.ProgressPercentage]]%
Your Position[[.Position]] / [[.QueueSize]]
Est. Wait Time~[[.EstimatedWaitTime]] min(s)

This page will refresh in [[.RefreshInterval]] seconds.

Debug Info:
[[.DebugInfo]]
` + fallbackHTML := `Service Queue

You're in the Queue

Our service is currently experiencing high demand. Please wait, and this page will refresh automatically.

[[.QueueData.ProgressPercentage]]%
Your Position[[.QueueData.Position]] / [[.QueueData.QueueSize]]
Est. Wait Time~[[.QueueData.EstimatedWaitTime]] min(s)

This page will refresh in [[.QueueData.RefreshInterval]] seconds.

Debug Info:
[[.QueueData.DebugInfo]]
` tmpl, err := template.New("FallbackQueuePage").Delims("[[", "]]").Parse(fallbackHTML) if err != nil { diff --git a/queue-manager_test.go b/queue-manager_test.go index 562be8b..191e225 100644 --- a/queue-manager_test.go +++ b/queue-manager_test.go @@ -4,7 +4,6 @@ package traefik_queue_manager import ( "bytes" "context" - "io" "log" "net/http" @@ -192,7 +191,7 @@ func TestGetClientID_NoCookies_IPUserAgentHash(t *testing.T) { id1, _ := qm.getClientID(rr1, req1) req2 := httptest.NewRequest("GET", "/", nil) - req2.RemoteAddr = "1.2.3.4:456" // Same IP, different port + req2.RemoteAddr = "1.2.3.4:456" // Same IP, different port req2.Header.Set("User-Agent", "TestAgent1") // Same User-Agent rr2 := httptest.NewRecorder() id2, _ := qm.getClientID(rr2, req2) @@ -227,7 +226,6 @@ func TestFileExists(t *testing.T) { f.Close() defer os.Remove(tempFileName) - if !fileExists(tempFileName) { t.Errorf("fileExists returned false for an existing file: %s", tempFileName) } @@ -236,7 +234,6 @@ func TestFileExists(t *testing.T) { } } - func TestServeHTTP_CapacityAvailable(t *testing.T) { cfg := CreateConfig() cfg.MaxEntries = 1 @@ -246,7 +243,6 @@ func TestServeHTTP_CapacityAvailable(t *testing.T) { var logBuf bytes.Buffer logger := log.New(&logBuf, "", 0) - nextCalled := false nextHandler := &mockNext{serveHTTPFunc: func(w http.ResponseWriter, r *http.Request) { nextCalled = true @@ -263,7 +259,6 @@ func TestServeHTTP_CapacityAvailable(t *testing.T) { defer qmInst.Stop() } - req := httptest.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -281,7 +276,7 @@ func TestServeHTTP_CapacityAvailable(t *testing.T) { func TestServeHTTP_NoCapacity_QueueUser(t *testing.T) { cfg := CreateConfig() - cfg.MaxEntries = 0 // Force no capacity + cfg.MaxEntries = 0 // Force no capacity cfg.QueuePageFile, _ = createTempFile(t, "Queue Page: [[.Position]]") // Valid template file defer os.Remove(cfg.QueuePageFile) cfg.Debug = true @@ -305,7 +300,6 @@ func TestServeHTTP_NoCapacity_QueueUser(t *testing.T) { defer qmInst.Stop() } - req := httptest.NewRequest("GET", "/", nil) rr := httptest.NewRecorder() @@ -322,20 +316,18 @@ func TestServeHTTP_NoCapacity_QueueUser(t *testing.T) { } } - func TestServeHTTP_MultipleUsers_QueueAndProceed(t *testing.T) { cfg := CreateConfig() cfg.MaxEntries = 1 cfg.InactivityTimeoutSeconds = 1 // Short for testing expiry cfg.CleanupIntervalSeconds = 1 // Short for testing cleanup - cfg.QueuePageFile, _ = createTempFile(t, "Queue: Pos [[.Position]] Size [[.QueueSize]] Wait [[.EstimatedWaitTime]]") + cfg.QueuePageFile, _ = createTempFile(t, "Queue: Pos [[.QueueData.Position]] Size [[.QueueData.QueueSize]] Wait [[.QueueData.EstimatedWaitTime]]") defer os.Remove(cfg.QueuePageFile) cfg.Debug = true var logBuf bytes.Buffer logger := log.New(&logBuf, "", 0) - var serviceAccessCount int var mu sync.Mutex // To protect serviceAccessCount @@ -405,10 +397,9 @@ func TestServeHTTP_MultipleUsers_QueueAndProceed(t *testing.T) { // Inactivity is 1s, Cleanup is 1s. Wait a bit longer. time.Sleep(time.Duration(cfg.InactivityTimeoutSeconds+cfg.CleanupIntervalSeconds+1) * time.Second) - // --- Client 2 again (should now get access) --- req3 := httptest.NewRequest("GET", "/client2-retry", nil) // New request path for clarity - req3.Header.Set("X-Test-Client-ID", "client2") // Same client ID + req3.Header.Set("X-Test-Client-ID", "client2") // Same client ID // Add cookie that client 2 would have received from its first (queued) request cookiesClient2 := rr2.Result().Cookies() for _, c := range cookiesClient2 { @@ -436,12 +427,11 @@ func TestServeHTTP_MultipleUsers_QueueAndProceed(t *testing.T) { mu.Unlock() } - func TestPrepareQueuePageData(t *testing.T) { qm := &QueueManager{ - config: CreateConfig(), - logger: log.New(io.Discard, "", 0), - queue: make([]Session, 0), + config: CreateConfig(), + logger: log.New(io.Discard, "", 0), + queue: make([]Session, 0), activeSessionIDs: make(map[string]bool), } qm.config.MinWaitTimeMinutes = 1 @@ -449,37 +439,58 @@ func TestPrepareQueuePageData(t *testing.T) { // Scenario 1: Empty queue, new user (pos 0) data1 := qm.prepareQueuePageData(0) // pos 0 (first in line) - if data1.Position != 1 { t.Errorf("Expected Position 1, got %d", data1.Position) } - if data1.QueueSize != 0 { t.Errorf("Expected QueueSize 0, got %d", data1.QueueSize) } - if data1.EstimatedWaitTime != 1 { t.Errorf("Expected EstimatedWaitTime %d, got %d", qm.config.MinWaitTimeMinutes, data1.EstimatedWaitTime) } // Min wait time - if data1.ProgressPercentage != 99 {t.Errorf("Expected ProgressPercentage 99, got %d", data1.ProgressPercentage)} - + if data1.Position != 1 { + t.Errorf("Expected Position 1, got %d", data1.Position) + } + if data1.QueueSize != 0 { + t.Errorf("Expected QueueSize 0, got %d", data1.QueueSize) + } + if data1.EstimatedWaitTime != 1 { + t.Errorf("Expected EstimatedWaitTime %d, got %d", qm.config.MinWaitTimeMinutes, data1.EstimatedWaitTime) + } // Min wait time + if data1.ProgressPercentage != 99 { + t.Errorf("Expected ProgressPercentage 99, got %d", data1.ProgressPercentage) + } // Scenario 2: Queue with 5 people, user is 3rd in line (pos 2) - qm.queue = make([]Session, 5) // Simulate 5 people in queue + qm.queue = make([]Session, 5) // Simulate 5 people in queue data2 := qm.prepareQueuePageData(2) // 0-indexed position 2 (3rd person) - if data2.Position != 3 { t.Errorf("Expected Position 3, got %d", data2.Position) } - if data2.QueueSize != 5 { t.Errorf("Expected QueueSize 5, got %d", data2.QueueSize) } + if data2.Position != 3 { + t.Errorf("Expected Position 3, got %d", data2.Position) + } + if data2.QueueSize != 5 { + t.Errorf("Expected QueueSize 5, got %d", data2.QueueSize) + } // Wait factor for pos 2 (0-indexed) is 0.3 + (2%5 * 0.08) = 0.3 + 0.16 = 0.46 // Raw wait = 2 * 0.46 = 0.92. Ceil(0.92) = 1. Max(1, MinWaitTime=1) = 1 - if data2.EstimatedWaitTime != 1 { t.Errorf("Expected EstimatedWaitTime 1, got %d", data2.EstimatedWaitTime) } + if data2.EstimatedWaitTime != 1 { + t.Errorf("Expected EstimatedWaitTime 1, got %d", data2.EstimatedWaitTime) + } // Progress: (5 - (2+1)) / 5 * 100 = (5-3)/5 * 100 = 2/5 * 100 = 40% - if data2.ProgressPercentage != 40 { t.Errorf("Expected ProgressPercentage 40, got %d", data2.ProgressPercentage) } - + if data2.ProgressPercentage != 40 { + t.Errorf("Expected ProgressPercentage 40, got %d", data2.ProgressPercentage) + } // Scenario 3: Queue with 1 person, user is 1st (pos 0) qm.queue = make([]Session, 1) data3 := qm.prepareQueuePageData(0) - if data3.Position != 1 { t.Errorf("Expected Position 1, got %d", data3.Position) } - if data3.QueueSize != 1 { t.Errorf("Expected QueueSize 1, got %d", data3.QueueSize) } - if data3.EstimatedWaitTime != 1 { t.Errorf("Expected EstimatedWaitTime %d, got %d", qm.config.MinWaitTimeMinutes, data3.EstimatedWaitTime) } + if data3.Position != 1 { + t.Errorf("Expected Position 1, got %d", data3.Position) + } + if data3.QueueSize != 1 { + t.Errorf("Expected QueueSize 1, got %d", data3.QueueSize) + } + if data3.EstimatedWaitTime != 1 { + t.Errorf("Expected EstimatedWaitTime %d, got %d", qm.config.MinWaitTimeMinutes, data3.EstimatedWaitTime) + } // Progress: (1 - (0+1)) / 1 * 100 = 0. Should be adjusted. // The logic is: if queueSize == 1 && positionInQueue == 0 -> 50% - if data3.ProgressPercentage != 50 {t.Errorf("Expected ProgressPercentage 50 for single item queue, got %d", data3.ProgressPercentage)} + if data3.ProgressPercentage != 50 { + t.Errorf("Expected ProgressPercentage 50 for single item queue, got %d", data3.ProgressPercentage) + } } - // Note: Add more comprehensive tests, especially for CleanupExpiredSessions scenarios // including hard session limits, and more edge cases for ServeHTTP. // Mocking time (time.Now()) would make these tests more robust and faster, -// but it adds complexity (e.g., using an interface for time or a library). \ No newline at end of file +// but it adds complexity (e.g., using an interface for time or a library). From baa2715d6e385a2258ce8fdc49e76c94a64e5422 Mon Sep 17 00:00:00 2001 From: Nicolas Guillard Date: Wed, 12 Nov 2025 09:01:02 +0100 Subject: [PATCH 3/3] fix(go): restore original go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9d0440c..72a1dd1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/hhftechnology/traefik-queue-manager -go 1.25 +go 1.19