mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
dfb5d1df54
* fix: make config read/write thread safe and fix some other similar issues 1. The config config has a lock, and safe methods to update and read the attributes 2. The config has methods to retrieve copies of specific attributes, such as the extyensions config, the auth config, and the authz config. These are needed, as the config object may mutate in the middle of an auth/authz requests, and we avoid partial configuration being applied for that request. 3. Fix an issue with the monitoring server not stopping when the controller is shut down. 4. Fix an issue with the HTPasswdWatcher not stopping when the background tasks are supposed to finish. 5. Fix some tests using hardcoded ports. Moved some of the methods which were on the main config to the auth, access control and extension configs Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
556 lines
18 KiB
Go
556 lines
18 KiB
Go
package api_test
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
|
|
"zotregistry.dev/zot/v2/pkg/api"
|
|
"zotregistry.dev/zot/v2/pkg/log"
|
|
test "zotregistry.dev/zot/v2/pkg/test/common"
|
|
)
|
|
|
|
func TestHTPasswdWatcherOriginal(t *testing.T) {
|
|
logger := log.NewLogger("DEBUG", "")
|
|
|
|
Convey("reload htpasswd", t, func(c C) {
|
|
username, _ := test.GenerateRandomString()
|
|
password1, _ := test.GenerateRandomString()
|
|
password2, _ := test.GenerateRandomString()
|
|
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password1))
|
|
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
htp := api.NewHTPasswd(logger)
|
|
|
|
htw, err := api.NewHTPasswdWatcher(htp, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
// Start the watcher goroutine
|
|
htw.Run()
|
|
|
|
defer htw.Close() //nolint: errcheck
|
|
|
|
_, present := htp.Get(username)
|
|
So(present, ShouldBeFalse)
|
|
|
|
err = htw.ChangeFile(htpasswdPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
// 1. Check user present and it has password1
|
|
ok, present := htp.Authenticate(username, password1)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
ok, present = htp.Authenticate(username, password2)
|
|
So(ok, ShouldBeFalse)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// 2. Change file
|
|
err = os.WriteFile(htpasswdPath, []byte(test.GetCredString(username, password2)), 0o600)
|
|
So(err, ShouldBeNil)
|
|
|
|
// 3. Give some time for the background task
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// 4. Check user present and now has password2
|
|
ok, present = htp.Authenticate(username, password1)
|
|
So(ok, ShouldBeFalse)
|
|
So(present, ShouldBeTrue)
|
|
|
|
ok, present = htp.Authenticate(username, password2)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
})
|
|
}
|
|
|
|
func TestHTPasswdWatcher(t *testing.T) {
|
|
logger := log.NewLogger("DEBUG", "")
|
|
|
|
Convey("Test HTPasswdWatcher comprehensive functionality", t, func() {
|
|
Convey("Test basic operations and lifecycle", func() {
|
|
// Create a buffer to capture log output
|
|
logBuffer, multiWriter := test.CreateLogCapturingWriter(os.Stdout)
|
|
capturingLogger := log.NewLoggerWithWriter("debug", multiWriter)
|
|
|
|
htp := api.NewHTPasswd(capturingLogger)
|
|
htw, err := api.NewHTPasswdWatcher(htp, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
// Test Run() and Close() operations
|
|
So(func() { htw.Run() }, ShouldNotPanic)
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(func() { htw.Run() }, ShouldNotPanic) // Idempotent
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(func() { htw.Close() }, ShouldNotPanic)
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw.Close(), ShouldBeNil) // Idempotent
|
|
|
|
// Verify goroutine termination
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test ChangeFile() operations and file watching", func() {
|
|
username1, _ := test.GenerateRandomString()
|
|
password1, _ := test.GenerateRandomString()
|
|
username2, _ := test.GenerateRandomString()
|
|
password2, _ := test.GenerateRandomString()
|
|
|
|
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
|
|
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetCredString(username2, password2))
|
|
|
|
defer os.Remove(htpasswdPath1)
|
|
defer os.Remove(htpasswdPath2)
|
|
|
|
htp := api.NewHTPasswd(logger)
|
|
htw, err := api.NewHTPasswdWatcher(htp, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
// Test ChangeFile() when not running
|
|
err = htw.ChangeFile(htpasswdPath1)
|
|
So(err, ShouldBeNil)
|
|
ok, present := htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Start watcher and test ChangeFile() when running
|
|
htw.Run()
|
|
defer htw.Close()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Change to second file
|
|
err = htw.ChangeFile(htpasswdPath2)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username2, password2)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
_, present = htp.Authenticate(username1, password1)
|
|
So(present, ShouldBeFalse)
|
|
|
|
// Test ChangeFile() to empty string (clear store)
|
|
err = htw.ChangeFile("")
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
_, present = htp.Authenticate(username2, password2)
|
|
So(present, ShouldBeFalse)
|
|
|
|
// Test ChangeFile() with non-existent file
|
|
err = htw.ChangeFile("/non/existent/path")
|
|
So(err, ShouldNotBeNil)
|
|
|
|
// Test file change detection and reload
|
|
err = htw.ChangeFile(htpasswdPath1)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Change file content and verify automatic reload
|
|
err = os.WriteFile(htpasswdPath1, []byte(test.GetCredString(username1, password2)), 0o600)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(100 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username1, password2)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Test multiple users
|
|
multiUserContent := test.GetCredString(username1, password1) + "\n" + test.GetCredString(username2, password2)
|
|
err = os.WriteFile(htpasswdPath1, []byte(multiUserContent), 0o600)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(100 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
ok, present = htp.Authenticate(username2, password2)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Test invalid content (clears store)
|
|
err = os.WriteFile(htpasswdPath1, []byte("invalid-content"), 0o600)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(100 * time.Millisecond)
|
|
_, present = htp.Authenticate(username1, password1)
|
|
So(present, ShouldBeFalse)
|
|
|
|
// Test empty file (clears store)
|
|
err = os.WriteFile(htpasswdPath1, []byte(""), 0o600)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(100 * time.Millisecond)
|
|
_, present = htp.Authenticate(username2, password2)
|
|
So(present, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("Test restart capability, edge cases, and file operations", func() {
|
|
// Create a buffer to capture log output
|
|
logBuffer, multiWriter := test.CreateLogCapturingWriter(os.Stdout)
|
|
capturingLogger := log.NewLoggerWithWriter("debug", multiWriter)
|
|
|
|
username1, _ := test.GenerateRandomString()
|
|
password1, _ := test.GenerateRandomString()
|
|
username2, _ := test.GenerateRandomString()
|
|
password2, _ := test.GenerateRandomString()
|
|
|
|
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
|
|
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetCredString(username2, password2))
|
|
|
|
defer os.Remove(htpasswdPath1)
|
|
defer os.Remove(htpasswdPath2)
|
|
|
|
htp := api.NewHTPasswd(capturingLogger)
|
|
htw, err := api.NewHTPasswdWatcher(htp, htpasswdPath1)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Test restart capability
|
|
htw.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
err = htw.ChangeFile(htpasswdPath1)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
ok, present := htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Close and restart
|
|
So(htw.Close(), ShouldBeNil)
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
htw.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Change file after restart
|
|
err = htw.ChangeFile(htpasswdPath2)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username2, password2)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Test file becomes inaccessible
|
|
os.Remove(htpasswdPath2)
|
|
time.Sleep(100 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username2, password2)
|
|
So(ok, ShouldBeTrue) // User should still be present
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Test file rename (should not trigger reload)
|
|
htpasswdPath3 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
|
|
defer os.Remove(htpasswdPath3)
|
|
err = htw.ChangeFile(htpasswdPath3)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
ok, present = htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
newPath := htpasswdPath3 + ".new"
|
|
err = os.Rename(htpasswdPath3, newPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(newPath)
|
|
time.Sleep(100 * time.Millisecond)
|
|
ok, _ = htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue) // User should still be present
|
|
|
|
// Test file permission change (should not trigger reload)
|
|
err = os.Chmod(newPath, 0o000)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer func() { _ = os.Chmod(newPath, 0o644) }()
|
|
time.Sleep(100 * time.Millisecond)
|
|
ok, _ = htp.Authenticate(username1, password1)
|
|
So(ok, ShouldBeTrue) // User should still be present
|
|
|
|
// Test with non-existent directory
|
|
htw2, err := api.NewHTPasswdWatcher(htp, "/non/existent/dir/htpasswd")
|
|
So(err, ShouldBeNil)
|
|
So(func() { htw2.Run() }, ShouldNotPanic)
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
// 1 termination message
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test with very long file path
|
|
longPath := "/tmp/"
|
|
for i := 0; i < 100; i++ {
|
|
longPath += "verylongdirname"
|
|
}
|
|
longPath += "/htpasswd"
|
|
htw3, err := api.NewHTPasswdWatcher(htp, longPath)
|
|
So(err, ShouldBeNil)
|
|
So(func() { htw3.Run() }, ShouldNotPanic)
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw3.Close(), ShouldBeNil)
|
|
// 1 termination message
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Clean up
|
|
So(htw.Close(), ShouldBeNil)
|
|
// 1 termination message
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test concurrent operations and goroutine cleanup", func() {
|
|
// Create a buffer to capture log output
|
|
logBuffer, multiWriter := test.CreateLogCapturingWriter(os.Stdout)
|
|
capturingLogger := log.NewLoggerWithWriter("debug", multiWriter)
|
|
|
|
username1, _ := test.GenerateRandomString()
|
|
password1, _ := test.GenerateRandomString()
|
|
username2, _ := test.GenerateRandomString()
|
|
password2, _ := test.GenerateRandomString()
|
|
|
|
htpasswdPath1 := test.MakeHtpasswdFileFromString(test.GetCredString(username1, password1))
|
|
htpasswdPath2 := test.MakeHtpasswdFileFromString(test.GetCredString(username2, password2))
|
|
|
|
defer os.Remove(htpasswdPath1)
|
|
defer os.Remove(htpasswdPath2)
|
|
|
|
htp := api.NewHTPasswd(capturingLogger)
|
|
htw, err := api.NewHTPasswdWatcher(htp, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
// Test concurrent Run() and Close()
|
|
go func() {
|
|
for i := 0; i < 5; i++ {
|
|
htw.Run()
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for i := 0; i < 5; i++ {
|
|
htw.Close()
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
So(func() { htw.Close() }, ShouldNotPanic)
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test concurrent ChangeFile() operations
|
|
htw.Run()
|
|
defer htw.Close()
|
|
|
|
go func() {
|
|
for i := 0; i < 3; i++ {
|
|
_ = htw.ChangeFile(htpasswdPath1)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for i := 0; i < 3; i++ {
|
|
_ = htw.ChangeFile(htpasswdPath2)
|
|
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// At least one user should be present
|
|
ok1, present1 := htp.Authenticate(username1, password1)
|
|
ok2, present2 := htp.Authenticate(username2, password2)
|
|
So(present1 || present2, ShouldBeTrue)
|
|
So(ok1 || ok2, ShouldBeTrue)
|
|
|
|
// Test goroutine cleanup with multiple verification methods
|
|
htw2, err := api.NewHTPasswdWatcher(htp, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
// Start watcher
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Close watcher
|
|
So(htw2.Close(), ShouldBeNil)
|
|
|
|
// Wait for goroutine to terminate (check log messages)
|
|
// 1 termination message
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Verify we can restart the watcher (indicates proper cleanup)
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
// 1 termination message
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test multiple Run/Close cycles
|
|
for i := 0; i < 3; i++ {
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
time.Sleep(50 * time.Millisecond) // Give time for termination
|
|
}
|
|
})
|
|
|
|
Convey("Test goroutine termination with comprehensive log verification", func() {
|
|
// Create a buffer to capture log output
|
|
logBuffer, multiWriter := test.CreateLogCapturingWriter(os.Stdout)
|
|
capturingLogger := log.NewLoggerWithWriter("debug", multiWriter)
|
|
|
|
// Test 1: Basic termination verification (no file watching)
|
|
htp1 := api.NewHTPasswd(capturingLogger)
|
|
htw1, err := api.NewHTPasswdWatcher(htp1, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
htw1.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw1.Close(), ShouldBeNil)
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test 2: File watching with fsnotify resources cleanup
|
|
username, _ := test.GenerateRandomString()
|
|
password, _ := test.GenerateRandomString()
|
|
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
|
|
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
htp2 := api.NewHTPasswd(capturingLogger)
|
|
htw2, err := api.NewHTPasswdWatcher(htp2, htpasswdPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Start watcher with file
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Load file to ensure watcher is active
|
|
err = htw2.ChangeFile(htpasswdPath)
|
|
So(err, ShouldBeNil)
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Close watcher and verify termination
|
|
So(htw2.Close(), ShouldBeNil)
|
|
// 1 + 1 = 2
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 2, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test 3: Multiple termination cycles with file watching
|
|
for i := 0; i < 3; i++ {
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
time.Sleep(50 * time.Millisecond) // Give time for termination
|
|
}
|
|
|
|
// Verify we have at least 3 termination messages so far (2 previous + 1 cycle = 3)
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 3, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test 4: Stress test with rapid cycles
|
|
for i := 0; i < 5; i++ {
|
|
htw2.Run()
|
|
time.Sleep(5 * time.Millisecond)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
time.Sleep(20 * time.Millisecond) // Give time for termination
|
|
}
|
|
|
|
// Verify we have at least 8 termination messages so far (3+5 = 8)
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 8, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Final verification: watcher should still work after all cycles
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
|
|
// Final verification of all termination messages with timeout
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 9, 5*time.Second), ShouldBeTrue) // 8+1 = 9
|
|
})
|
|
|
|
Convey("Test malformed htpasswd files", func() {
|
|
// Create a buffer to capture log output
|
|
logBuffer, multiWriter := test.CreateLogCapturingWriter(os.Stdout)
|
|
capturingLogger := log.NewLoggerWithWriter("debug", multiWriter)
|
|
|
|
username, _ := test.GenerateRandomString()
|
|
password, _ := test.GenerateRandomString()
|
|
|
|
htp := api.NewHTPasswd(capturingLogger)
|
|
|
|
// Test file with only colons (malformed)
|
|
colonPath := test.MakeHtpasswdFileFromString(":::")
|
|
defer os.Remove(colonPath)
|
|
htw1, err := api.NewHTPasswdWatcher(htp, colonPath)
|
|
So(err, ShouldBeNil)
|
|
htw1.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
_ = htw1.ChangeFile(colonPath)
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
// The malformed file creates an entry with empty username, so test that
|
|
_, present := htp.Authenticate("", "anything")
|
|
So(present, ShouldBeTrue) // Empty username entry exists but auth fails
|
|
ok, _ := htp.Authenticate("", "anything")
|
|
So(ok, ShouldBeFalse) // But authentication should fail
|
|
So(htw1.Close(), ShouldBeNil)
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
|
|
// Test file with empty lines and comments
|
|
content := "\n\n" + test.GetCredString(username, password) + "\n# comment\n"
|
|
commentedPath := test.MakeHtpasswdFileFromString(content)
|
|
|
|
defer os.Remove(commentedPath)
|
|
htw2, err := api.NewHTPasswdWatcher(htp, commentedPath)
|
|
So(err, ShouldBeNil)
|
|
htw2.Run()
|
|
time.Sleep(10 * time.Millisecond)
|
|
_ = htw2.ChangeFile(commentedPath)
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
ok, _ = htp.Authenticate(username, password)
|
|
So(ok, ShouldBeTrue) // User should be loaded (comments/empty lines ignored)
|
|
So(htw2.Close(), ShouldBeNil)
|
|
// 1 termination message
|
|
So(test.WaitForLogMessages(logBuffer, "htpasswd watcher terminating...", 1, 5*time.Second), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Test ChangeFile with nil watcher and empty filepath", func() {
|
|
// Create a logger (no need for log capture since we're not testing goroutine termination)
|
|
capturingLogger := log.NewLogger("debug", "")
|
|
|
|
username, _ := test.GenerateRandomString()
|
|
password, _ := test.GenerateRandomString()
|
|
|
|
htp := api.NewHTPasswd(capturingLogger)
|
|
htw, err := api.NewHTPasswdWatcher(htp, "")
|
|
So(err, ShouldBeNil)
|
|
|
|
// Load some initial data
|
|
htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
|
|
defer os.Remove(htpasswdPath)
|
|
|
|
// Load initial file (this will populate the store)
|
|
err = htw.ChangeFile(htpasswdPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Verify user is loaded
|
|
ok, present := htp.Authenticate(username, password)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
|
|
// Now test the edge case: ChangeFile with empty string when watcher is nil
|
|
// (watcher is nil because we haven't called Run() yet)
|
|
err = htw.ChangeFile("")
|
|
So(err, ShouldBeNil) // Should not return an error
|
|
|
|
// Verify that the store was cleared
|
|
ok, present = htp.Authenticate(username, password)
|
|
So(ok, ShouldBeFalse) // Authentication should fail
|
|
So(present, ShouldBeFalse) // User should not be present
|
|
|
|
// Test that we can still load a file after clearing
|
|
err = htw.ChangeFile(htpasswdPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
// Verify user is loaded again
|
|
ok, present = htp.Authenticate(username, password)
|
|
So(ok, ShouldBeTrue)
|
|
So(present, ShouldBeTrue)
|
|
})
|
|
})
|
|
}
|