Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] bc24a85bc7 chore(deps): bump docker/setup-buildx-action from 4.0.0 to 4.1.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd...d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 12:15:16 +00:00
6 changed files with 26 additions and 487 deletions
+6 -6
View File
@@ -163,7 +163,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -221,7 +221,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -279,7 +279,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -337,7 +337,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -391,7 +391,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Docker meta
id: meta
@@ -430,7 +430,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Docker meta
id: meta
+6 -6
View File
@@ -133,7 +133,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -188,7 +188,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -243,7 +243,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -298,7 +298,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
@@ -352,7 +352,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Docker meta
id: meta
@@ -393,7 +393,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Docker meta
id: meta
-117
View File
@@ -1,117 +0,0 @@
package bootstrap
import (
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/model"
)
func TestSetupStore_UnknownDriver(t *testing.T) {
tests := []struct {
driver string
wantErr string
}{
{
driver: "mysql",
wantErr: `unknown database driver "mysql": valid values are sqlite, postgres, memory`,
},
{
driver: "redis",
wantErr: `unknown database driver "redis": valid values are sqlite, postgres, memory`,
},
{
driver: "baddriver",
wantErr: `unknown database driver "baddriver": valid values are sqlite, postgres, memory`,
},
}
for _, tt := range tests {
t.Run("driver_"+tt.driver, func(t *testing.T) {
app := NewBootstrapApp(model.Config{
Database: model.DatabaseConfig{
Driver: tt.driver,
},
})
store, err := app.SetupStore()
assert.Nil(t, store)
require.Error(t, err)
assert.Equal(t, tt.wantErr, err.Error())
})
}
}
func TestSetupStore_Memory(t *testing.T) {
app := NewBootstrapApp(model.Config{
Database: model.DatabaseConfig{
Driver: "memory",
},
})
store, err := app.SetupStore()
require.NoError(t, err)
assert.NotNil(t, store)
}
func TestSetupStore_SQLite_ExplicitDriver(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "test.db")
app := NewBootstrapApp(model.Config{
Database: model.DatabaseConfig{
Driver: "sqlite",
Path: dbPath,
},
})
store, err := app.SetupStore()
require.NoError(t, err)
assert.NotNil(t, store)
}
func TestSetupStore_SQLite_DefaultDriver(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "default.db")
app := NewBootstrapApp(model.Config{
Database: model.DatabaseConfig{
Driver: "",
Path: dbPath,
},
})
store, err := app.SetupStore()
require.NoError(t, err)
assert.NotNil(t, store)
}
func TestSetupStore_Postgres_InvalidURL(t *testing.T) {
app := NewBootstrapApp(model.Config{
Database: model.DatabaseConfig{
Driver: "postgres",
Path: "not-a-valid-postgres-url",
},
})
store, err := app.SetupStore()
// sql.Open does not fail on a bad URL for pgx — it only fails on first use.
// The error should come from pgxmigrate.WithInstance when the DB is actually
// pinged / connected, so we expect either success-with-error or an error here.
// What matters is that the postgres case is reached (i.e., no "unknown driver" error).
if err != nil {
assert.False(t, strings.Contains(err.Error(), "unknown database driver"))
assert.Nil(t, store)
}
}
func TestSetupStore_ErrorMessageIncludesPostgres(t *testing.T) {
app := NewBootstrapApp(model.Config{
Database: model.DatabaseConfig{
Driver: "oracle",
},
})
_, err := app.SetupStore()
require.Error(t, err)
assert.Contains(t, err.Error(), "postgres")
assert.Contains(t, err.Error(), "sqlite")
assert.Contains(t, err.Error(), "memory")
}
-90
View File
@@ -1,90 +0,0 @@
package model
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
// TestDatabaseConfig_DescriptionMentionsPostgres verifies that the DatabaseConfig
// Driver field description explicitly lists "postgres" as a valid value, reflecting
// the newly added PostgreSQL support.
func TestDatabaseConfig_DescriptionMentionsPostgres(t *testing.T) {
rt := reflect.TypeOf(DatabaseConfig{})
driverField, ok := rt.FieldByName("Driver")
assert.True(t, ok, "DatabaseConfig should have a Driver field")
description := driverField.Tag.Get("description")
assert.Contains(t, description, "postgres", "DatabaseConfig.Driver description should mention postgres as a valid value")
assert.Contains(t, description, "sqlite", "DatabaseConfig.Driver description should mention sqlite as a valid value")
assert.Contains(t, description, "memory", "DatabaseConfig.Driver description should mention memory as a valid value")
}
// TestDatabaseConfig_PathDescriptionMentionsConnectionURL verifies that the Path
// field description covers both SQLite file path and PostgreSQL connection URL usage.
func TestDatabaseConfig_PathDescriptionMentionsConnectionURL(t *testing.T) {
rt := reflect.TypeOf(DatabaseConfig{})
pathField, ok := rt.FieldByName("Path")
assert.True(t, ok, "DatabaseConfig should have a Path field")
description := pathField.Tag.Get("description")
assert.Contains(t, description, "postgres",
"DatabaseConfig.Path description should mention postgres to clarify connection URL usage")
}
// TestIPConfig_NoBypassField verifies that the Bypass field has been removed
// from IPConfig as part of the PR changes. IP bypass lists are now only
// configured at the per-app ACL level.
func TestIPConfig_NoBypassField(t *testing.T) {
rt := reflect.TypeOf(IPConfig{})
_, hasBypass := rt.FieldByName("Bypass")
assert.False(t, hasBypass, "IPConfig should not have a Bypass field after PR changes")
}
// TestIPConfig_HasAllowAndBlock ensures the remaining Allow and Block fields
// are still present in IPConfig after the Bypass removal.
func TestIPConfig_HasAllowAndBlock(t *testing.T) {
rt := reflect.TypeOf(IPConfig{})
_, hasAllow := rt.FieldByName("Allow")
assert.True(t, hasAllow, "IPConfig should still have an Allow field")
_, hasBlock := rt.FieldByName("Block")
assert.True(t, hasBlock, "IPConfig should still have a Block field")
}
// TestOAuthServiceConfig_NoWhitelistField verifies that the per-provider Whitelist
// and WhitelistFile fields have been removed from OAuthServiceConfig. The global
// OAuthWhitelist on OAuthConfig/RuntimeConfig is now the only whitelist.
func TestOAuthServiceConfig_NoWhitelistField(t *testing.T) {
rt := reflect.TypeOf(OAuthServiceConfig{})
_, hasWhitelist := rt.FieldByName("Whitelist")
assert.False(t, hasWhitelist, "OAuthServiceConfig should not have a Whitelist field after PR changes")
_, hasWhitelistFile := rt.FieldByName("WhitelistFile")
assert.False(t, hasWhitelistFile, "OAuthServiceConfig should not have a WhitelistFile field after PR changes")
}
// TestOAuthServiceConfig_CoreFieldsPreserved ensures that removing the whitelist
// fields did not inadvertently drop unrelated fields.
func TestOAuthServiceConfig_CoreFieldsPreserved(t *testing.T) {
rt := reflect.TypeOf(OAuthServiceConfig{})
for _, fieldName := range []string{"ClientID", "ClientSecret", "ClientSecretFile", "Scopes", "RedirectURL", "AuthURL", "TokenURL", "UserinfoURL"} {
_, ok := rt.FieldByName(fieldName)
assert.True(t, ok, "OAuthServiceConfig should still have a %s field", fieldName)
}
}
// TestDatabaseConfig_ZeroValue ensures DatabaseConfig is usable as a zero value
// with the expected default (empty string) driver, which falls back to sqlite.
func TestDatabaseConfig_ZeroValue(t *testing.T) {
var cfg DatabaseConfig
assert.Equal(t, "", cfg.Driver, "zero-value Driver should be an empty string (defaults to sqlite)")
assert.Equal(t, "", cfg.Path, "zero-value Path should be an empty string")
}
-162
View File
@@ -1,162 +0,0 @@
package postgres
import (
"context"
"database/sql"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/repository"
)
// TestMapErr verifies that mapErr translates known sentinel errors and
// passes through all other errors unchanged.
func TestMapErr(t *testing.T) {
sentinel := errors.New("some other error")
tests := []struct {
name string
input error
want error
isWant bool // use errors.Is check
}{
{
name: "nil passes through unchanged",
input: nil,
want: nil,
isWant: false,
},
{
name: "sql.ErrNoRows maps to repository.ErrNotFound",
input: sql.ErrNoRows,
want: repository.ErrNotFound,
isWant: true,
},
{
name: "wrapped sql.ErrNoRows maps to repository.ErrNotFound",
input: fmt.Errorf("wrapped: %w", sql.ErrNoRows),
want: repository.ErrNotFound,
isWant: true,
},
{
name: "arbitrary error passes through unchanged",
input: sentinel,
want: sentinel,
isWant: true,
},
{
name: "wrapped arbitrary error passes through unchanged",
input: fmt.Errorf("outer: %w", sentinel),
want: fmt.Errorf("outer: %w", sentinel),
isWant: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := mapErr(tt.input)
if tt.input == nil {
assert.Nil(t, got)
return
}
if tt.isWant {
assert.True(t, errors.Is(got, tt.want), "expected errors.Is(%v, %v) to be true, got %v", got, tt.want, got)
} else {
// For wrapped-arbitrary-error passthrough: the original wrapped error is returned as-is
assert.Equal(t, tt.input, got)
}
})
}
}
// TestMapErr_ErrNoRows_IsRepositoryErrNotFound specifically asserts the contract
// that callers outside the package can detect repository.ErrNotFound using errors.Is.
func TestMapErr_ErrNoRows_IsRepositoryErrNotFound(t *testing.T) {
result := mapErr(sql.ErrNoRows)
require.NotNil(t, result)
assert.True(t, errors.Is(result, repository.ErrNotFound))
// Must NOT still be sql.ErrNoRows after mapping
assert.False(t, errors.Is(result, sql.ErrNoRows))
}
// TestMapErr_OtherError_IsNotRepositoryErrNotFound ensures unrecognised errors
// are NOT silently converted to ErrNotFound.
func TestMapErr_OtherError_IsNotRepositoryErrNotFound(t *testing.T) {
someErr := errors.New("connection refused")
result := mapErr(someErr)
require.NotNil(t, result)
assert.False(t, errors.Is(result, repository.ErrNotFound))
assert.True(t, errors.Is(result, someErr))
}
// TestNewStore ensures that NewStore returns a value satisfying the
// repository.Store interface (compile-time verified) and is not nil.
func TestNewStore(t *testing.T) {
q := New(nil) // Queries with a nil DBTX — adequate for construction checks
var store repository.Store = NewStore(q)
assert.NotNil(t, store)
}
// mockDBTX is a minimal DBTX implementation that returns a configurable error.
type mockDBTX struct {
err error
rowErr error
}
func (m *mockDBTX) ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) {
return nil, m.err
}
func (m *mockDBTX) PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) {
return nil, m.err
}
func (m *mockDBTX) QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) {
return nil, m.err
}
func (m *mockDBTX) QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row {
// *sql.Row cannot be constructed without internals; returning nil causes a
// nil-dereference in callers, so we can only test ExecContext-backed methods.
return nil
}
// TestStore_DeleteSession_PropagatesError verifies that an error returned by the
// underlying DBTX is forwarded (possibly mapped) by the Store wrapper.
func TestStore_DeleteSession_PropagatesError(t *testing.T) {
customErr := errors.New("exec error")
mock := &mockDBTX{err: customErr}
store := NewStore(New(mock))
err := store.DeleteSession(context.Background(), "some-uuid")
require.Error(t, err)
// The error is not ErrNoRows, so it must be passed through as-is.
assert.True(t, errors.Is(err, customErr))
}
// TestStore_DeleteOidcCode_PropagatesError verifies error propagation for a
// different delete method.
func TestStore_DeleteOidcCode_PropagatesError(t *testing.T) {
customErr := errors.New("exec error")
mock := &mockDBTX{err: customErr}
store := NewStore(New(mock))
err := store.DeleteOidcCode(context.Background(), "some-hash")
require.Error(t, err)
assert.True(t, errors.Is(err, customErr))
}
// TestStore_DeleteExpiredSessions_PropagatesErrNoRowsAsNotFound verifies that
// sql.ErrNoRows is mapped to repository.ErrNotFound through the Store wrapper.
func TestStore_DeleteExpiredSessions_PropagatesError(t *testing.T) {
customErr := errors.New("db unavailable")
mock := &mockDBTX{err: customErr}
store := NewStore(New(mock))
err := store.DeleteExpiredSessions(context.Background(), 0)
require.Error(t, err)
assert.True(t, errors.Is(err, customErr))
}
+14 -106
View File
@@ -8,105 +8,7 @@ import (
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
func newTestAuthService(whitelist []string) *AuthService {
log := logger.NewLogger().WithTestConfig()
log.Init()
return &AuthService{
log: log,
runtime: model.RuntimeConfig{
OAuthWhitelist: whitelist,
},
}
}
func TestIsEmailWhitelisted(t *testing.T) {
tests := []struct {
name string
whitelist []string
email string
expected bool
}{
{
name: "empty whitelist denies all",
whitelist: []string{},
email: "user@example.com",
expected: false,
},
{
name: "nil whitelist denies all",
whitelist: nil,
email: "user@example.com",
expected: false,
},
{
name: "matching email is allowed",
whitelist: []string{"user@example.com"},
email: "user@example.com",
expected: true,
},
{
name: "non-matching email is denied",
whitelist: []string{"user@example.com"},
email: "other@example.com",
expected: false,
},
{
name: "multiple entries, matching email is allowed",
whitelist: []string{"alice@example.com", "bob@example.com"},
email: "bob@example.com",
expected: true,
},
{
name: "multiple entries, non-matching email is denied",
whitelist: []string{"alice@example.com", "bob@example.com"},
email: "charlie@example.com",
expected: false,
},
{
name: "regex pattern matches email",
whitelist: []string{"/@example\\.com$/"},
email: "anyone@example.com",
expected: true,
},
{
name: "regex pattern does not match different domain",
whitelist: []string{"/@example\\.com$/"},
email: "anyone@other.com",
expected: false,
},
{
name: "wildcard domain pattern with regex",
whitelist: []string{"/^.+@mycompany\\.org$/"},
email: "employee@mycompany.org",
expected: true,
},
{
name: "only global whitelist is used, not any per-provider list",
whitelist: []string{"global@example.com"},
email: "global@example.com",
expected: true,
},
{
name: "whitespace-only entries are handled gracefully",
whitelist: []string{" "},
email: "user@example.com",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
auth := newTestAuthService(tt.whitelist)
result := auth.IsEmailWhitelisted(tt.email)
assert.Equal(t, tt.expected, result)
})
}
}
// TestIsEmailWhitelistedNoPerProviderList verifies the new behaviour where
// per-provider whitelist overrides are no longer applied; only the global
// OAuthWhitelist is consulted regardless of which OAuth provider was used.
func TestIsEmailWhitelistedNoPerProviderList(t *testing.T) {
func TestIsEmailWhitelistedUsesProviderSpecificList(t *testing.T) {
log := logger.NewLogger().WithTestConfig()
log.Init()
@@ -114,18 +16,24 @@ func TestIsEmailWhitelistedNoPerProviderList(t *testing.T) {
log: log,
runtime: model.RuntimeConfig{
OAuthWhitelist: []string{"global@example.com"},
// OAuthProviders still present but their Whitelist field has been removed
OAuthProviders: map[string]model.OAuthServiceConfig{
"github": {
ClientID: "github-client-id",
Whitelist: []string{"github@example.com"},
},
"pocketid": {
Whitelist: []string{"pocket@example.com"},
},
"gitlab": {
Whitelist: []string{},
},
},
},
}
// Global whitelist allows this email regardless of provider
assert.True(t, auth.IsEmailWhitelisted("global@example.com"))
// Global whitelist denies this email even though it was previously
// allowed by a provider-specific list in the old implementation
assert.False(t, auth.IsEmailWhitelisted("provider-only@example.com"))
assert.True(t, auth.IsEmailWhitelisted("github", "github@example.com"))
assert.False(t, auth.IsEmailWhitelisted("github", "pocket@example.com"))
assert.True(t, auth.IsEmailWhitelisted("pocketid", "pocket@example.com"))
assert.True(t, auth.IsEmailWhitelisted("google", "global@example.com"))
assert.True(t, auth.IsEmailWhitelisted("gitlab", "global@example.com"))
assert.False(t, auth.IsEmailWhitelisted("gitlab", "unknown@example.com"))
}