feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
197
internal/distworker/config_test.go
Обычный файл
197
internal/distworker/config_test.go
Обычный файл
@@ -0,0 +1,197 @@
|
||||
package distworker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestHTTPConfigFromEnv_Defaults verifies the documented defaults when no
|
||||
// HTTP-related env vars are set: 0.0.0.0:27401 and empty URL / login /
|
||||
// password. The default port was bumped from 7401 to 27401 to avoid
|
||||
// colliding with the main RSMon app when the worker is co-located.
|
||||
func TestHTTPConfigFromEnv_Defaults(t *testing.T) {
|
||||
t.Setenv("WORKER_HOST", "")
|
||||
t.Setenv("WORKER_PORT", "")
|
||||
t.Setenv("WORKER_URL", "")
|
||||
t.Setenv("WORKER_LOGIN", "")
|
||||
t.Setenv("WORKER_PASSWORD", "")
|
||||
|
||||
cfg := HTTPConfigFromEnv()
|
||||
assert.Equal(t, DefaultHTTPHost, cfg.Host, "host should default to 0.0.0.0")
|
||||
assert.Equal(t, DefaultHTTPPort, cfg.Port, "port should default to 27401")
|
||||
assert.Equal(t, "", cfg.URL)
|
||||
assert.Equal(t, "", cfg.Login)
|
||||
assert.Equal(t, "", cfg.Password)
|
||||
assert.False(t, cfg.IsAuthConfigured())
|
||||
}
|
||||
|
||||
// TestHTTPConfigFromEnv_Overrides verifies the env vars win over the
|
||||
// defaults when explicitly set, including a non-default port.
|
||||
func TestHTTPConfigFromEnv_Overrides(t *testing.T) {
|
||||
t.Setenv("WORKER_HOST", "127.0.0.1")
|
||||
t.Setenv("WORKER_PORT", "9100")
|
||||
t.Setenv("WORKER_URL", "https://worker.example.com")
|
||||
t.Setenv("WORKER_LOGIN", "ops")
|
||||
t.Setenv("WORKER_PASSWORD", "s3cret")
|
||||
|
||||
cfg := HTTPConfigFromEnv()
|
||||
assert.Equal(t, "127.0.0.1", cfg.Host)
|
||||
assert.Equal(t, 9100, cfg.Port)
|
||||
assert.Equal(t, "https://worker.example.com", cfg.URL)
|
||||
assert.Equal(t, "ops", cfg.Login)
|
||||
assert.Equal(t, "s3cret", cfg.Password)
|
||||
assert.True(t, cfg.IsAuthConfigured())
|
||||
}
|
||||
|
||||
// TestHTTPConfigFromEnv_PortGarbageFallsBackToDefault covers the "operator
|
||||
// fat-fingered WORKER_PORT" case: the helper must not panic and must
|
||||
// fall back to DefaultHTTPPort instead of starting on a zero port.
|
||||
func TestHTTPConfigFromEnv_PortGarbageFallsBackToDefault(t *testing.T) {
|
||||
t.Setenv("WORKER_PORT", "not-a-port")
|
||||
|
||||
cfg := HTTPConfigFromEnv()
|
||||
assert.Equal(t, DefaultHTTPPort, cfg.Port,
|
||||
"unparseable WORKER_PORT should fall back to the default")
|
||||
}
|
||||
|
||||
// TestHTTPConfigFromEnv_PortOutOfRangeFallsBackToDefault covers zero and
|
||||
// out-of-range port values which are rejected by url/strconv semantics.
|
||||
func TestHTTPConfigFromEnv_PortOutOfRangeFallsBackToDefault(t *testing.T) {
|
||||
for _, v := range []string{"0", "-1", "70000"} {
|
||||
t.Run("port="+v, func(t *testing.T) {
|
||||
t.Setenv("WORKER_PORT", v)
|
||||
cfg := HTTPConfigFromEnv()
|
||||
assert.Equal(t, DefaultHTTPPort, cfg.Port,
|
||||
"WORKER_PORT=%q should fall back to the default", v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateHTTPConfig_LoginXORPasswordRejected covers the central
|
||||
// invariant: a mixed login/password state is a config bug and must
|
||||
// fail fast.
|
||||
func TestValidateHTTPConfig_LoginXORPasswordRejected(t *testing.T) {
|
||||
t.Run("only login set", func(t *testing.T) {
|
||||
err := ValidateHTTPConfig(HTTPConfig{
|
||||
Host: "0.0.0.0", Port: 27401,
|
||||
Login: "ops", Password: "",
|
||||
}, false)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "WORKER_LOGIN and WORKER_PASSWORD")
|
||||
})
|
||||
t.Run("only password set", func(t *testing.T) {
|
||||
err := ValidateHTTPConfig(HTTPConfig{
|
||||
Host: "0.0.0.0", Port: 27401,
|
||||
Login: "", Password: "s3cret",
|
||||
}, false)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "WORKER_LOGIN and WORKER_PASSWORD")
|
||||
})
|
||||
}
|
||||
|
||||
// TestValidateHTTPConfig_BothEmptyAllowedWhenNotListening reflects the
|
||||
// Task 2 deferral: while the HTTP listener is not started (Task 3),
|
||||
// leaving both unset is permitted so a worker that does not yet need
|
||||
// the web app can still start.
|
||||
func TestValidateHTTPConfig_BothEmptyAllowedWhenNotListening(t *testing.T) {
|
||||
err := ValidateHTTPConfig(HTTPConfig{
|
||||
Host: "0.0.0.0", Port: 27401,
|
||||
Login: "", Password: "",
|
||||
}, false)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestValidateHTTPConfig_BothEmptyRejectedWhenListening confirms that
|
||||
// when Task 3 actually starts the listener, an unauthenticated listener
|
||||
// is refused.
|
||||
func TestValidateHTTPConfig_BothEmptyRejectedWhenListening(t *testing.T) {
|
||||
err := ValidateHTTPConfig(HTTPConfig{
|
||||
Host: "0.0.0.0", Port: 27401,
|
||||
Login: "", Password: "",
|
||||
}, true)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "refused to start")
|
||||
}
|
||||
|
||||
// TestValidateHTTPConfig_URLAbsoluteRequired covers the relative-URL
|
||||
// rejection: peer workers and the main app need a concrete origin.
|
||||
func TestValidateHTTPConfig_URLAbsoluteRequired(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
url string
|
||||
}{
|
||||
{"relative path", "/worker"},
|
||||
{"missing scheme", "example.com"},
|
||||
{"missing host", "https://"},
|
||||
{"empty after trim", ""},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := HTTPConfig{Host: "0.0.0.0", Port: 27401, URL: tc.url}
|
||||
err := ValidateHTTPConfig(cfg, false)
|
||||
// empty url is allowed
|
||||
if tc.url == "" {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateHTTPConfig_URLSchemeAllowed covers the two accepted schemes.
|
||||
func TestValidateHTTPConfig_URLSchemeAllowed(t *testing.T) {
|
||||
for _, scheme := range []string{"http", "https"} {
|
||||
t.Run(scheme, func(t *testing.T) {
|
||||
cfg := HTTPConfig{
|
||||
Host: "0.0.0.0", Port: 27401,
|
||||
URL: scheme + "://localhost:27401",
|
||||
}
|
||||
assert.NoError(t, ValidateHTTPConfig(cfg, false))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateHTTPConfig_URLSchemeRejected covers non-http(s) schemes.
|
||||
func TestValidateHTTPConfig_URLSchemeRejected(t *testing.T) {
|
||||
for _, scheme := range []string{"ftp", "file", "ws", "wss"} {
|
||||
t.Run(scheme, func(t *testing.T) {
|
||||
cfg := HTTPConfig{
|
||||
Host: "0.0.0.0", Port: 27401,
|
||||
URL: scheme + "://localhost:27401",
|
||||
}
|
||||
err := ValidateHTTPConfig(cfg, false)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, strings.ToLower(err.Error()), "scheme must be http or https")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWarnInsecurePublicURL exercises the warning helper: only http on
|
||||
// non-loopback hosts should warn. The returned host is the parsed host
|
||||
// (host:port when present) so the caller can log a useful target.
|
||||
func TestWarnInsecurePublicURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
url string
|
||||
wantWarn bool
|
||||
wantHost string
|
||||
}{
|
||||
{"empty", "", false, ""},
|
||||
{"https production", "https://worker.example.com", false, "worker.example.com"},
|
||||
{"http loopback", "http://localhost:27401", false, "localhost:27401"},
|
||||
{"http 127.0.0.1", "http://127.0.0.1:27401", false, "127.0.0.1:27401"},
|
||||
{"http 0.0.0.0", "http://0.0.0.0:27401", false, "0.0.0.0:27401"},
|
||||
{"http production", "http://worker.example.com", true, "worker.example.com"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
host, warn := WarnInsecurePublicURL(tc.url)
|
||||
assert.Equal(t, tc.wantWarn, warn)
|
||||
assert.Equal(t, tc.wantHost, host)
|
||||
})
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user