mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-28 15:02:28 +00:00
8b19760f4e
Many of our tests rely on helpers that predate features which are now available in the standard library. This patch replaces them with the standard equivalents: - MustTempDir/RemoveIfOk -> t.ArtifactDir() or t.TempDir() (depending on whether it is useful to keep the data on request). - GetFreePort + waitForServer -> opening the listeners before starting the servers. To support this, localrpc.Server gains a Serve method. We may re-introduce these in the future if more use cases appear. - WaitFor and real-time timing checks -> testing/synctest. - context.Background() -> t.Context(). - Benchmark b.N loops -> b.Loop(). - reflect.DeepEqual -> cmp.Diff, and proto.Equal for protobufs.
329 lines
7.5 KiB
Go
329 lines
7.5 KiB
Go
package userdb
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// Create a database with the given content on a temporary filename. Return
|
|
// the filename, or an error if there were errors creating it.
|
|
func mustCreateDB(t *testing.T, content string) string {
|
|
t.Helper()
|
|
f, err := os.CreateTemp(t.ArtifactDir(), "userdb_test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if _, err := f.WriteString(content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("file: %q", f.Name())
|
|
return f.Name()
|
|
}
|
|
|
|
func dbEquals(a, b *DB) bool {
|
|
return proto.Equal(a.db, b.db)
|
|
}
|
|
|
|
var emptyDB = &DB{
|
|
db: &ProtoDB{Users: map[string]*Password{}},
|
|
}
|
|
|
|
// Test various cases of loading an empty/broken database.
|
|
func TestEmptyLoad(t *testing.T) {
|
|
cases := []struct {
|
|
desc string
|
|
content string
|
|
fatal bool
|
|
fatalErr error
|
|
}{
|
|
{"empty file", "", false, nil},
|
|
{"invalid ", "users: < invalid >", true, nil},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
testOneLoad(t, c.desc, c.content, c.fatal, c.fatalErr)
|
|
}
|
|
}
|
|
|
|
func testOneLoad(t *testing.T, desc, content string, fatal bool, fatalErr error) {
|
|
fname := mustCreateDB(t, content)
|
|
db, err := Load(fname)
|
|
if fatal {
|
|
if err == nil {
|
|
t.Errorf("case %q: expected error loading, got nil", desc)
|
|
}
|
|
if fatalErr != nil && fatalErr != err {
|
|
t.Errorf("case %q: expected error %v, got %v", desc, fatalErr, err)
|
|
}
|
|
} else if !fatal && err != nil {
|
|
t.Fatalf("case %q: error loading database: %v", desc, err)
|
|
}
|
|
|
|
if db != nil && !dbEquals(db, emptyDB) {
|
|
t.Errorf("case %q: DB not empty: %#v", desc, db.db.Users)
|
|
}
|
|
}
|
|
|
|
func mustLoad(t *testing.T, fname string) *DB {
|
|
db, err := Load(fname)
|
|
if err != nil {
|
|
t.Fatalf("error loading database: %v", err)
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
func TestWrite(t *testing.T) {
|
|
fname := mustCreateDB(t, "")
|
|
db := mustLoad(t, fname)
|
|
|
|
if err := db.Write(); err != nil {
|
|
t.Fatalf("error writing database: %v", err)
|
|
}
|
|
|
|
// Load again, check it works and it's still empty.
|
|
db = mustLoad(t, fname)
|
|
if !dbEquals(emptyDB, db) {
|
|
t.Fatalf("expected %v, got %v", emptyDB, db)
|
|
}
|
|
|
|
// Add users, write, and load again.
|
|
if err := db.AddUser("user1", "passwd1"); err != nil {
|
|
t.Fatalf("failed to add user1: %v", err)
|
|
}
|
|
if err := db.AddUser("ñoño", "añicos"); err != nil {
|
|
t.Fatalf("failed to add ñoño: %v", err)
|
|
}
|
|
if err := db.AddDeniedUser("ñaca"); err != nil {
|
|
t.Fatalf("failed to add ñaca: %v", err)
|
|
}
|
|
if err := db.Write(); err != nil {
|
|
t.Fatalf("error writing database: %v", err)
|
|
}
|
|
|
|
db = mustLoad(t, fname)
|
|
for _, name := range []string{"user1", "ñoño", "ñaca"} {
|
|
if !db.Exists(name) {
|
|
t.Errorf("user %q not in database", name)
|
|
}
|
|
if db.db.Users[name].GetScheme() == nil {
|
|
t.Errorf("user %q missing scheme: %#v", name, db.db.Users[name])
|
|
}
|
|
}
|
|
|
|
// Check various user and password combinations, not all valid.
|
|
combinations := []struct {
|
|
user, passwd string
|
|
expected bool
|
|
}{
|
|
{"user1", "passwd1", true},
|
|
{"user1", "passwd", false},
|
|
{"user1", "passwd12", false},
|
|
{"ñoño", "añicos", true},
|
|
{"ñoño", "anicos", false},
|
|
{"ñaca", "", false},
|
|
{"ñaca", "lalala", false},
|
|
{"notindb", "something", false},
|
|
{"", "", false},
|
|
{" ", " ", false},
|
|
}
|
|
for _, c := range combinations {
|
|
if db.Authenticate(c.user, c.passwd) != c.expected {
|
|
t.Errorf("auth(%q, %q) != %v", c.user, c.passwd, c.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
fname := t.TempDir() + "/userdb"
|
|
db1 := New(fname)
|
|
db1.AddUser("user", "passwd")
|
|
db1.Write()
|
|
|
|
db2, err := Load(fname)
|
|
if err != nil {
|
|
t.Fatalf("error loading: %v", err)
|
|
}
|
|
|
|
if !dbEquals(db1, db2) {
|
|
t.Errorf("databases differ. db1:%v != db2:%v", db1, db2)
|
|
}
|
|
}
|
|
|
|
func TestInvalidUsername(t *testing.T) {
|
|
fname := mustCreateDB(t, "")
|
|
db := mustLoad(t, fname)
|
|
|
|
// Names that are invalid.
|
|
names := []string{
|
|
// Contain various types of spaces.
|
|
" ", " ", "a b", "ñ ñ", "a\xa0b", "a\x85b", "a\nb", "a\tb", "a\xffb",
|
|
|
|
// Contain characters not allowed by PRECIS.
|
|
"\u00b9", "\u2163",
|
|
|
|
// Names that are not normalized, but would otherwise be valid.
|
|
"A", "Ñ",
|
|
}
|
|
for _, name := range names {
|
|
err := db.AddUser(name, "passwd")
|
|
if err == nil {
|
|
t.Errorf("AddUser(%q) worked, expected it to fail", name)
|
|
}
|
|
|
|
err = db.AddDeniedUser(name)
|
|
if err == nil {
|
|
t.Errorf("AddDeniedUser(%q) worked, expected it to fail", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func plainPassword(p string) *Password {
|
|
return &Password{
|
|
Scheme: &Password_Plain{
|
|
Plain: &Plain{Password: []byte(p)},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Test the plain scheme. Note we don't expect to use it in cases other than
|
|
// debugging, but it should be functional for that purpose.
|
|
func TestPlainScheme(t *testing.T) {
|
|
fname := mustCreateDB(t, "")
|
|
db := mustLoad(t, fname)
|
|
|
|
db.db.Users["user"] = plainPassword("pass word")
|
|
err := db.Write()
|
|
if err != nil {
|
|
t.Errorf("Write failed: %v", err)
|
|
}
|
|
|
|
db = mustLoad(t, fname)
|
|
if !db.Authenticate("user", "pass word") {
|
|
t.Errorf("failed plain authentication")
|
|
}
|
|
if db.Authenticate("user", "wrong") {
|
|
t.Errorf("plain authentication worked but it shouldn't")
|
|
}
|
|
}
|
|
|
|
// Test the denied scheme.
|
|
func TestDeniedScheme(t *testing.T) {
|
|
fname := mustCreateDB(t, "")
|
|
db := mustLoad(t, fname)
|
|
|
|
db.db.Users["user"] = &Password{Scheme: &Password_Denied{}}
|
|
err := db.Write()
|
|
if err != nil {
|
|
t.Errorf("Write failed: %v", err)
|
|
}
|
|
|
|
db = mustLoad(t, fname)
|
|
if db.Authenticate("user", "anything") {
|
|
t.Errorf("denied authentication worked but it shouldn't")
|
|
}
|
|
}
|
|
|
|
func TestReload(t *testing.T) {
|
|
content := "users:< key: 'u1' value:< plain:< password: 'pass' >>>"
|
|
fname := mustCreateDB(t, content)
|
|
db := mustLoad(t, fname)
|
|
|
|
// Add a valid line to the file.
|
|
content += "users:< key: 'u2' value:< plain:< password: 'pass' >>>"
|
|
os.WriteFile(fname, []byte(content), 0660)
|
|
|
|
err := db.Reload()
|
|
if err != nil {
|
|
t.Errorf("Reload failed: %v", err)
|
|
}
|
|
if len(db.db.Users) != 2 {
|
|
t.Errorf("expected 2 users, got %d", len(db.db.Users))
|
|
}
|
|
|
|
// And now a broken one.
|
|
content += "users:< invalid >"
|
|
os.WriteFile(fname, []byte(content), 0660)
|
|
|
|
err = db.Reload()
|
|
if err == nil {
|
|
t.Errorf("expected error, got nil")
|
|
}
|
|
if len(db.db.Users) != 2 {
|
|
t.Errorf("expected 2 users, got %d", len(db.db.Users))
|
|
}
|
|
|
|
// Delete the file (which is not considered an error).
|
|
os.Remove(fname)
|
|
err = db.Reload()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if len(db.db.Users) != 0 {
|
|
t.Errorf("expected 0 users, got %d", len(db.db.Users))
|
|
}
|
|
}
|
|
|
|
func TestRemoveUser(t *testing.T) {
|
|
fname := mustCreateDB(t, "")
|
|
db := mustLoad(t, fname)
|
|
|
|
if ok := db.RemoveUser("unknown"); ok {
|
|
t.Errorf("removal of unknown user succeeded")
|
|
}
|
|
|
|
if err := db.AddUser("user", "passwd"); err != nil {
|
|
t.Fatalf("error adding user: %v", err)
|
|
}
|
|
|
|
if ok := db.RemoveUser("unknown"); ok {
|
|
t.Errorf("removal of unknown user succeeded")
|
|
}
|
|
|
|
if ok := db.RemoveUser("user"); !ok {
|
|
t.Errorf("removal of existing user failed")
|
|
}
|
|
|
|
if ok := db.RemoveUser("user"); ok {
|
|
t.Errorf("removal of unknown user succeeded")
|
|
}
|
|
}
|
|
|
|
func TestExists(t *testing.T) {
|
|
fname := mustCreateDB(t, "")
|
|
db := mustLoad(t, fname)
|
|
|
|
if db.Exists("unknown") {
|
|
t.Errorf("unknown user exists")
|
|
}
|
|
|
|
if err := db.AddUser("user", "passwd"); err != nil {
|
|
t.Fatalf("error adding user: %v", err)
|
|
}
|
|
|
|
if db.Exists("unknown") {
|
|
t.Errorf("unknown user exists")
|
|
}
|
|
|
|
if !db.Exists("user") {
|
|
t.Errorf("known user does not exist")
|
|
}
|
|
|
|
if !db.Exists("user") {
|
|
t.Errorf("known user does not exist")
|
|
}
|
|
|
|
if err := db.AddDeniedUser("denieduser"); err != nil {
|
|
t.Fatalf("error adding user: %v", err)
|
|
}
|
|
|
|
if !db.Exists("denieduser") {
|
|
t.Errorf("known (denied) user does not exist")
|
|
}
|
|
}
|