mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-07-08 11:47:38 +00:00
eee83f9dc6
Tests / Integration Tests (push) Failing after 1s
Tests / Unit Tests (push) Failing after 24s
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Successful in 59s
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Successful in 58s
Build , Vet Test, and Lint / Build (push) Successful in 4m37s
Build , Vet Test, and Lint / Lint Code (push) Failing after 5m35s
* Introduced QueryMode to select between stored procedure and direct SQL execution. * Implemented dbCapability to probe for stored procedure existence. * Added table names configuration for direct SQL operations. * Updated DatabaseTwoFactorProvider to support query mode and table names. * Implemented direct SQL methods mirroring stored procedures for TOTP operations. * Added tests for query mode logic and table names validation.
166 lines
5.0 KiB
Go
166 lines
5.0 KiB
Go
package security
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"testing"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
func openTestSQLite(t *testing.T) *sql.DB {
|
|
t.Helper()
|
|
db, err := sql.Open("sqlite3", ":memory:")
|
|
if err != nil {
|
|
t.Fatalf("failed to open sqlite db: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
return db
|
|
}
|
|
|
|
func TestShouldUseProcedure_ModeProcedure_AlwaysTrue(t *testing.T) {
|
|
db := openTestSQLite(t)
|
|
c := newDBCapability()
|
|
if !c.ShouldUseProcedure(context.Background(), ModeProcedure, db, "resolvespec_login") {
|
|
t.Error("ModeProcedure should always resolve to true")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseProcedure_ModeDirect_AlwaysFalse(t *testing.T) {
|
|
db := openTestSQLite(t)
|
|
c := newDBCapability()
|
|
if c.ShouldUseProcedure(context.Background(), ModeDirect, db, "resolvespec_login") {
|
|
t.Error("ModeDirect should always resolve to false")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseProcedure_Auto_SQLite_ResolvesDirect(t *testing.T) {
|
|
db := openTestSQLite(t)
|
|
c := newDBCapability()
|
|
if c.ShouldUseProcedure(context.Background(), ModeAuto, db, "resolvespec_login") {
|
|
t.Error("ModeAuto against a SQLite connection should resolve to Direct (false)")
|
|
}
|
|
}
|
|
|
|
func TestShouldUseProcedure_Auto_UnknownDriver_DefaultsToProcedure(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("failed to create mock db: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
c := newDBCapability()
|
|
if !c.ShouldUseProcedure(context.Background(), ModeAuto, db, "resolvespec_login") {
|
|
t.Error("ModeAuto against an unrecognized driver (e.g. a test double) should default to Procedure (true) to preserve existing behavior")
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Errorf("unfulfilled expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestShouldUseProcedure_CachesResult(t *testing.T) {
|
|
db := openTestSQLite(t)
|
|
c := newDBCapability()
|
|
first := c.ShouldUseProcedure(context.Background(), ModeAuto, db, "resolvespec_login")
|
|
if _, ok := c.funcExists.Load("resolvespec_login"); !ok {
|
|
t.Error("expected result to be cached")
|
|
}
|
|
second := c.ShouldUseProcedure(context.Background(), ModeAuto, db, "resolvespec_login")
|
|
if first != second {
|
|
t.Errorf("cached result changed: first=%v second=%v", first, second)
|
|
}
|
|
}
|
|
|
|
func TestDBCapability_Reset_ClearsCache(t *testing.T) {
|
|
c := newDBCapability()
|
|
c.funcExists.Store("resolvespec_login", true)
|
|
c.reset()
|
|
if _, ok := c.funcExists.Load("resolvespec_login"); ok {
|
|
t.Error("reset should clear all cached entries")
|
|
}
|
|
}
|
|
|
|
func TestProbeFunctionExists_Postgres_FunctionExists(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("failed to create mock db: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
rows := sqlmock.NewRows([]string{"exists"}).AddRow(true)
|
|
mock.ExpectQuery(`SELECT EXISTS \(SELECT 1 FROM pg_proc WHERE proname = \$1 LIMIT 1\)`).
|
|
WithArgs("resolvespec_login").
|
|
WillReturnRows(rows)
|
|
|
|
if !probeFunctionExists(context.Background(), db, "resolvespec_login") {
|
|
t.Error("expected probeFunctionExists to return true")
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Errorf("unfulfilled expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestProbeFunctionExists_Postgres_FunctionMissing(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("failed to create mock db: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
rows := sqlmock.NewRows([]string{"exists"}).AddRow(false)
|
|
mock.ExpectQuery(`SELECT EXISTS \(SELECT 1 FROM pg_proc WHERE proname = \$1 LIMIT 1\)`).
|
|
WithArgs("resolvespec_login").
|
|
WillReturnRows(rows)
|
|
|
|
if probeFunctionExists(context.Background(), db, "resolvespec_login") {
|
|
t.Error("expected probeFunctionExists to return false")
|
|
}
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
t.Errorf("unfulfilled expectations: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestProbeFunctionExists_QueryError_ReturnsFalse(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
if err != nil {
|
|
t.Fatalf("failed to create mock db: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
mock.ExpectQuery(`SELECT EXISTS \(SELECT 1 FROM pg_proc WHERE proname = \$1 LIMIT 1\)`).
|
|
WithArgs("resolvespec_login").
|
|
WillReturnError(sql.ErrConnDone)
|
|
|
|
if probeFunctionExists(context.Background(), db, "resolvespec_login") {
|
|
t.Error("expected probeFunctionExists to return false on query error")
|
|
}
|
|
}
|
|
|
|
func TestProbeFunctionExists_NilDB(t *testing.T) {
|
|
if probeFunctionExists(context.Background(), nil, "resolvespec_login") {
|
|
t.Error("expected probeFunctionExists(nil) to return false")
|
|
}
|
|
}
|
|
|
|
func TestRewritePlaceholders_NonPostgres_NoOp(t *testing.T) {
|
|
db := openTestSQLite(t)
|
|
q := "SELECT * FROM users WHERE id = ? AND name = ?"
|
|
if got := rewritePlaceholders(db, q); got != q {
|
|
t.Errorf("rewritePlaceholders on non-Postgres = %q, want unchanged %q", got, q)
|
|
}
|
|
}
|
|
|
|
func TestGenerateSessionToken_Format(t *testing.T) {
|
|
token, err := generateSessionToken()
|
|
if err != nil {
|
|
t.Fatalf("generateSessionToken() error = %v", err)
|
|
}
|
|
if len(token) < len("sess_")+64+2 {
|
|
t.Errorf("generateSessionToken() = %q, unexpected length", token)
|
|
}
|
|
if token[:5] != "sess_" {
|
|
t.Errorf("generateSessionToken() = %q, want prefix 'sess_'", token)
|
|
}
|
|
}
|