Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.1.5"
".": "1.2.0"
}
2 changes: 1 addition & 1 deletion .release-please-manifest.premain.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.1.5"
".": "1.2.0-rc"
}
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## [1.2.0](https://github.com/theory-cloud/TableTheory/compare/v1.1.5...v1.2.0) (2026-01-22)


### Features

* **mocks:** add transaction builder mock ([ea39672](https://github.com/theory-cloud/TableTheory/commit/ea39672edffd22bf24b1471e244c14b79f06211d))
* **mocks:** add transaction builder mock ([16ab5a5](https://github.com/theory-cloud/TableTheory/commit/16ab5a5d7b22d1087973f96a5c69b2c3a3796c3e))


### Bug Fixes

* **ci:** retry git fetch in branch-version-sync ([1b4d855](https://github.com/theory-cloud/TableTheory/commit/1b4d8557fe66c5c333846469369d9e5285cc1232))
* **mocks:** satisfy lint gates ([a9cd117](https://github.com/theory-cloud/TableTheory/commit/a9cd1170fc200489369b76f098635321ed3d81c0))
* **premain:** restore prerelease version alignment ([9b07cdb](https://github.com/theory-cloud/TableTheory/commit/9b07cdb7df5e69be8012374f742d89252ffde942))

## [1.2.0-rc](https://github.com/theory-cloud/TableTheory/compare/v1.1.5...v1.2.0-rc) (2026-01-22)


### Features

* **mocks:** add transaction builder mock ([ea39672](https://github.com/theory-cloud/TableTheory/commit/ea39672edffd22bf24b1471e244c14b79f06211d))
* **mocks:** add transaction builder mock ([16ab5a5](https://github.com/theory-cloud/TableTheory/commit/16ab5a5d7b22d1087973f96a5c69b2c3a3796c3e))


### Bug Fixes

* **ci:** retry git fetch in branch-version-sync ([1b4d855](https://github.com/theory-cloud/TableTheory/commit/1b4d8557fe66c5c333846469369d9e5285cc1232))
* **mocks:** satisfy lint gates ([a9cd117](https://github.com/theory-cloud/TableTheory/commit/a9cd1170fc200489369b76f098635321ed3d81c0))
* **premain:** restore prerelease version alignment ([9b07cdb](https://github.com/theory-cloud/TableTheory/commit/9b07cdb7df5e69be8012374f742d89252ffde942))

## [1.1.5](https://github.com/theory-cloud/TableTheory/compare/v1.1.4...v1.1.5) (2026-01-20)


Expand Down
2 changes: 1 addition & 1 deletion hgm-infra/evidence/hgm-rubric-report.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://hgm.pai.dev/schemas/hgm-rubric-report.schema.json",
"schemaVersion": 1,
"timestamp": "2026-01-19T05:20:17Z",
"timestamp": "2026-01-23T01:42:09Z",
"pack": {
"version": "816465a1618d",
"digest": "896aed16549928f21626fb4effe9bb6236fc60292a8f50bae8ce77e873ac775b"
Expand Down
31 changes: 30 additions & 1 deletion pkg/mocks/README_SIMPLE_EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,35 @@ mockUpdateBuilder.On("Set", "Email", "new@email.com").Return(mockUpdateBuilder)
mockUpdateBuilder.On("Execute").Return(nil)
```

### Pattern 7: Transactions (`TransactWrite`)
If your code uses the transaction DSL:

```go
err := db.TransactWrite(ctx, func(tx core.TransactionBuilder) error {
tx.WithContext(ctx)
tx.UpdateWithBuilder(&User{ID: "123"}, func(ub core.UpdateBuilder) error {
ub.Set("Status", "active")
return nil
})
return nil
})
```

You can mock it without boilerplate `.Run(...)` calls by providing a `MockTransactionBuilder`:

```go
mockDB := new(mocks.MockExtendedDB)
mockTx := new(mocks.MockTransactionBuilder)

// Tell the ExtendedDB mock which builder to run the callback with
mockDB.TransactWriteBuilder = mockTx

mockDB.On("TransactWrite", ctx, mock.Anything).Return(nil)
mockTx.On("WithContext", ctx).Return(mockTx)
mockTx.On("UpdateWithBuilder", mock.Anything, mock.Anything, mock.Anything).Return(mockTx)
mockTx.On("Execute").Return(nil)
```

## 🚨 Common Mistakes

### ❌ Mistake 1: Forgetting AssertExpectations
Expand Down Expand Up @@ -191,4 +220,4 @@ Check out these files for complete, working examples:
5. **Always verify expectations** - call `AssertExpectations(t)`
6. **Use `mock.Anything`** when you don't care about specific arguments

Happy testing! 🎉
Happy testing! 🎉
33 changes: 31 additions & 2 deletions pkg/mocks/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,37 @@ func (m *MockDB) Model(model any) core.Query {

// Transaction executes a function within a database transaction
func (m *MockDB) Transaction(fn func(tx *core.Tx) error) error {
args := m.Called(fn)
return args.Error(0)
if fn == nil {
args := m.Called(fn)
return args.Error(0)
}

var (
callbackInvoked bool
callbackErr error
)

wrapped := func(tx *core.Tx) error {
callbackInvoked = true
callbackErr = fn(tx)
return callbackErr
}

args := m.Called(wrapped)

if err := args.Error(0); err != nil {
return err
}

if !callbackInvoked {
tx := &core.Tx{}
tx.SetDB(m)
if err := wrapped(tx); err != nil {
return err
}
}

return callbackErr
}

// Migrate runs all pending migrations
Expand Down
97 changes: 89 additions & 8 deletions pkg/mocks/extended_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import (
// mockDB.On("Model", &User{}).Return(mockQuery)
// mockQuery.On("Create").Return(nil)
type MockExtendedDB struct {
// TransactWriteBuilder is used when TransactWrite auto-executes the provided callback.
// If nil, a new MockTransactionBuilder is created for each call.
TransactWriteBuilder core.TransactionBuilder

// TransactionFuncTx is passed to TransactionFunc when auto-executing callbacks.
TransactionFuncTx any

MockDB // Embed MockDB to inherit base methods
}

Expand Down Expand Up @@ -81,23 +88,97 @@ func (m *MockExtendedDB) WithLambdaTimeoutBuffer(buffer time.Duration) core.DB {

// TransactionFunc executes a function within a full transaction context
func (m *MockExtendedDB) TransactionFunc(fn func(tx any) error) error {
args := m.Called(fn)
return args.Error(0)
if fn == nil {
args := m.Called(fn)
return args.Error(0)
}

var (
callbackInvoked bool
callbackErr error
)

wrapped := func(tx any) error {
callbackInvoked = true
callbackErr = fn(tx)
return callbackErr
}

args := m.Called(wrapped)
if err := args.Error(0); err != nil {
return err
}

if !callbackInvoked {
if err := wrapped(m.TransactionFuncTx); err != nil {
return err
}
}

return callbackErr
}

// Transact returns a transaction builder mock
func (m *MockExtendedDB) Transact() core.TransactionBuilder {
args := m.Called()
if builder, ok := args.Get(0).(core.TransactionBuilder); ok {
return builder
}
return nil
return mustTransactionBuilder(args.Get(0))
}

// TransactWrite executes a function with a transaction builder
func (m *MockExtendedDB) TransactWrite(ctx context.Context, fn func(core.TransactionBuilder) error) error {
args := m.Called(ctx, fn)
return args.Error(0)
if fn == nil {
args := m.Called(ctx, fn)
return args.Error(0)
}

builder := m.TransactWriteBuilder
if builder == nil {
builder = new(MockTransactionBuilder)
}

var (
callbackInvoked bool
callbackInvokedDuringCall bool
callbackErr error
builderUsed core.TransactionBuilder
)

inCalled := true
wrapped := func(tx core.TransactionBuilder) error {
callbackInvoked = true
if inCalled {
callbackInvokedDuringCall = true
}
builderUsed = tx
callbackErr = fn(tx)
return callbackErr
}

args := m.Called(ctx, wrapped)
inCalled = false

if err := args.Error(0); err != nil {
return err
}

if !callbackInvoked {
if err := wrapped(builder); err != nil {
return err
}
}

if callbackErr != nil {
return callbackErr
}

if !callbackInvokedDuringCall {
if builderUsed == nil {
builderUsed = builder
}
return builderUsed.Execute()
}

return nil
}

// NewMockExtendedDB creates a new MockExtendedDB with sensible defaults
Expand Down
3 changes: 3 additions & 0 deletions pkg/mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ type (
// DB is an alias for MockDB to allow shorter declarations
DB = MockDB

// TransactionBuilder is an alias for MockTransactionBuilder to simplify usage in tests.
TransactionBuilder = MockTransactionBuilder

// UpdateBuilder is an alias for MockUpdateBuilder to allow shorter declarations
UpdateBuilder = MockUpdateBuilder

Expand Down
1 change: 1 addition & 0 deletions pkg/mocks/mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,6 @@ func TestTypeAliases(t *testing.T) {
// These should compile without issues
_ = new(mocks.Query)
_ = new(mocks.DB)
_ = new(mocks.TransactionBuilder)
_ = new(mocks.UpdateBuilder)
}
11 changes: 11 additions & 0 deletions pkg/mocks/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ func mustUpdateBuilder(v any) core.UpdateBuilder {
return builder
}

func mustTransactionBuilder(v any) core.TransactionBuilder {
if v == nil {
return nil
}
builder, ok := v.(core.TransactionBuilder)
if !ok {
panic("unexpected type: expected core.TransactionBuilder")
}
return builder
}

// MockQuery is a mock implementation of the core.Query interface.
// It can be used for unit testing code that depends on TableTheory queries.
//
Expand Down
Loading
Loading