Refactor database interface to use *sql.DB directly

Simplified the database layer by removing custom interface wrappers
and using standard library *sql.DB and *sql.Tx types directly.

Changes:
- Removed DBConnection and DBTransaction interfaces from database.go
- Removed NewDBConnection() wrapper function
- Updated TokenGenerator to use *sql.DB instead of DBConnection
- Updated all validation and revocation methods to accept *sql.Tx
- Updated TableManager to work with *sql.DB directly
- Updated all tests to use db.Begin() instead of custom wrappers
- Fixed GeneratorConfig.DB field (was DBConn)
- Updated documentation in doc.go with correct API usage

Benefits:
- Simpler API with fewer abstractions
- Works directly with database/sql standard library
- Compatible with GORM (via gormDB.DB()) and Bun (share same *sql.DB)
- Easier to understand and maintain
- No unnecessary wrapper layers

Breaking changes:
- GeneratorConfig.DBConn renamed to GeneratorConfig.DB
- Removed NewDBConnection() function - pass *sql.DB directly
- ValidateAccess/ValidateRefresh now accept *sql.Tx instead of DBTransaction
- Token.Revoke/CheckNotRevoked now accept *sql.Tx instead of DBTransaction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-11 17:39:30 +11:00
parent 557e9812e6
commit 1b25e2f0a5
44 changed files with 3728 additions and 294 deletions

273
hws/errors_test.go Normal file
View File

@@ -0,0 +1,273 @@
package hws_test
import (
"bytes"
"context"
"io"
"net/http"
"net/http/httptest"
"testing"
"git.haelnorr.com/h/golib/hws"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type goodPage struct{}
type badPage struct{}
func goodRender(code int) (hws.ErrorPage, error) {
return goodPage{}, nil
}
func badRender1(code int) (hws.ErrorPage, error) {
return badPage{}, nil
}
func badRender2(code int) (hws.ErrorPage, error) {
return nil, errors.New("I'm an error")
}
func (g goodPage) Render(ctx context.Context, w io.Writer) error {
w.Write([]byte("Test write to ResponseWriter"))
return nil
}
func (b badPage) Render(ctx context.Context, w io.Writer) error {
return nil
}
func Test_AddErrorPage(t *testing.T) {
var buf bytes.Buffer
server := createTestServer(t, &buf)
goodRender := goodRender
badRender1 := badRender1
badRender2 := badRender2
tests := []struct {
name string
renderer hws.ErrorPageFunc
valid bool
}{
{
name: "Valid Renderer",
renderer: goodRender,
valid: true,
},
{
name: "Invalid Renderer 1",
renderer: badRender1,
valid: false,
},
{
name: "Invalid Renderer 2",
renderer: badRender2,
valid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := server.AddErrorPage(tt.renderer)
if tt.valid {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}
func Test_ThrowError(t *testing.T) {
var buf bytes.Buffer
server := createTestServer(t, &buf)
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
t.Run("Server not started", func(t *testing.T) {
err := server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "Error",
Error: errors.New("Error"),
})
assert.Error(t, err)
})
startTestServer(t, server)
defer server.Shutdown(t.Context())
tests := []struct {
name string
request *http.Request
error hws.HWSError
valid bool
}{
{
name: "No HWSError.Status code",
request: nil,
error: hws.HWSError{},
valid: false,
},
{
name: "Negative HWSError.Status code",
request: nil,
error: hws.HWSError{StatusCode: -1},
valid: false,
},
{
name: "No HWSError.Message",
request: nil,
error: hws.HWSError{StatusCode: http.StatusInternalServerError},
valid: false,
},
{
name: "No HWSError.Error",
request: nil,
error: hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
},
valid: false,
},
{
name: "No request provided",
request: nil,
error: hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
},
valid: false,
},
{
name: "Valid",
request: httptest.NewRequest("GET", "/", nil),
error: hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
},
valid: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rr := httptest.NewRecorder()
err := server.ThrowError(rr, tt.request, tt.error)
if tt.valid {
assert.NoError(t, err)
} else {
t.Log(err)
assert.Error(t, err)
}
})
}
t.Run("Log level set correctly", func(t *testing.T) {
buf.Reset()
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
err := server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
Level: hws.ErrorWARN,
})
assert.NoError(t, err)
_, err = buf.ReadString([]byte(" ")[0])
loglvl, err := buf.ReadString([]byte(" ")[0])
assert.NoError(t, err)
if loglvl != "\x1b[33mWRN\x1b[0m " {
err = errors.New("Log level not set correctly")
}
assert.NoError(t, err)
buf.Reset()
err = server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
})
assert.NoError(t, err)
_, err = buf.ReadString([]byte(" ")[0])
loglvl, err = buf.ReadString([]byte(" ")[0])
assert.NoError(t, err)
if loglvl != "\x1b[31mERR\x1b[0m " {
err = errors.New("Log level not set correctly")
}
assert.NoError(t, err)
})
t.Run("Error page doesnt render if no error page set", func(t *testing.T) {
// Must be run before adding the error page to the test server
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
err := server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
RenderErrorPage: true,
})
assert.NoError(t, err)
body := rr.Body.String()
if body != "" {
assert.Error(t, nil)
}
})
t.Run("Error page renders", func(t *testing.T) {
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
// Adding the error page will carry over to all future tests and cant be undone
server.AddErrorPage(goodRender)
err := server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
RenderErrorPage: true,
})
assert.NoError(t, err)
body := rr.Body.String()
if body == "" {
assert.Error(t, nil)
}
})
t.Run("Error page doesnt render if no told to render", func(t *testing.T) {
// Error page already added to server
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
err := server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
})
assert.NoError(t, err)
body := rr.Body.String()
if body != "" {
assert.Error(t, nil)
}
})
server.Shutdown(t.Context())
t.Run("Doesn't error if no logger added to server", func(t *testing.T) {
server, err := hws.NewServer(&hws.Config{
Host: "127.0.0.1",
Port: randomPort(),
})
require.NoError(t, err)
err = server.AddRoutes(hws.Route{
Path: "/",
Method: hws.MethodGET,
Handler: testHandler,
})
require.NoError(t, err)
err = server.Start(t.Context())
require.NoError(t, err)
<-server.Ready()
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
err = server.ThrowError(rr, req, hws.HWSError{
StatusCode: http.StatusInternalServerError,
Message: "An error occured",
Error: errors.New("Error"),
})
assert.NoError(t, err)
})
}