package database_test import ( "strings" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/webhooker/internal/database" ) // usernameAtLimit is exactly MaxUsernameBytes long, built from a // two-byte character. A check that counted characters rather than bytes // would see half the length and let the one-byte-longer name through. func usernameAtLimit() string { return strings.Repeat("é", database.MaxUsernameBytes/2) } func TestUserCreate_RejectsOverlongUsername(t *testing.T) { t.Parallel() db := startedTestDB(t) err := db.Create(&database.User{ Username: usernameAtLimit() + "x", Password: "hash", }).Error require.ErrorIs(t, err, database.ErrUsernameTooLong) } func TestUserCreate_AcceptsUsernameAtLimit(t *testing.T) { t.Parallel() db := startedTestDB(t) require.NoError(t, db.Create(&database.User{ Username: usernameAtLimit(), Password: "hash", }).Error) } // TestUsersTable_EnforcesUsernameLimitWithoutTheModel inserts with raw // SQL, as a path that bypassed User.BeforeSave would, so only the // table's check constraint stands between it and an over-long // username. Accepting the name at the limit and refusing the next byte // also pins the constraint's number to MaxUsernameBytes. func TestUsersTable_EnforcesUsernameLimitWithoutTheModel(t *testing.T) { t.Parallel() db := startedTestDB(t) insert := "INSERT INTO users (id, username, password) VALUES (?, ?, ?)" require.NoError(t, db.Exec( insert, uuid.New().String(), usernameAtLimit(), "hash", ).Error) err := db.Exec( insert, uuid.New().String(), usernameAtLimit()+"x", "hash", ).Error require.Error(t, err) assert.Contains(t, err.Error(), "CHECK constraint failed") }