Skip to content

Commit

Permalink
chore: ApplyInsert tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jferrl committed Apr 27, 2024
1 parent 634c6c6 commit f111700
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 19 deletions.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ go 1.22.0

require (
cloud.google.com/go/spanner v1.60.0
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/googleapis/gax-go/v2 v2.12.2
google.golang.org/api v0.169.0
google.golang.org/grpc v1.62.1
)

require (
cloud.google.com/go v0.112.1 // indirect
cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.6 // indirect
cloud.google.com/go/longrunning v0.5.5 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
Expand Down Expand Up @@ -43,8 +47,8 @@ require (
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.169.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
Expand Down
3 changes: 1 addition & 2 deletions hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"cloud.google.com/go/spanner"
)

// Hook defines an action that can be executed before or after a method.
// Hook defines an action that can be executed during a mutation.
type Hook int

const (
Expand All @@ -21,5 +21,4 @@ const (
)

// HookFunc is a trigger function associated with a kind of mutation.
// It executed to generate the mutations to be applied.
type HookFunc func(context.Context) []*spanner.Mutation
4 changes: 3 additions & 1 deletion internal/sql/ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ CREATE TABLE Users (
Id STRING(36) NOT NULL,
Name STRING(MAX) NOT NULL,
Email STRING(MAX) NOT NULL,
) PRIMARY KEY (Id);
CreatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
UpdatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (Id)
25 changes: 20 additions & 5 deletions internal/user.yo.go → internal/user/user.yo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/yo_db.yo.go → internal/user/yo_db.yo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions yo.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,14 @@ func (m *Model[T]) ApplyDelete(ctx context.Context) (time.Time, error) {

func (m *Model[T]) readWriteTxn(ctx context.Context, h Hook) (time.Time, error) {
return m.Client.ReadWriteTransaction(ctx, func(ctx context.Context, rwt *spanner.ReadWriteTransaction) error {
var mutations []*spanner.Mutation
muts := []*spanner.Mutation{m.Insert(ctx)}

if actions, ok := m.hooks[h]; ok {
for _, f := range actions {
mutations = append(mutations, f(ctx)...)
muts = append(muts, f(ctx)...)
}
}

mutations = append(mutations, m.Insert(ctx))

return rwt.BufferWrite(mutations)
return rwt.BufferWrite(muts)
})
}
162 changes: 159 additions & 3 deletions yo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,36 @@ package yowrap

import (
"context"
"os"
"sort"
"testing"

"cloud.google.com/go/spanner"
database "cloud.google.com/go/spanner/admin/database/apiv1"
"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
"cloud.google.com/go/spanner/spannertest"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/jferrl/yowrap/internal"
"github.com/jferrl/yowrap/internal/user"
"google.golang.org/api/option"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

func TestModel_Insert(t *testing.T) {
ctx := context.Background()

type fields struct {
model *internal.User
model *user.User
}
tests := []struct {
name string
fields fields
}{
{
name: "yowrap model is able to expose the Insert method of the embedded model",
fields: fields{model: &internal.User{
fields: fields{model: &user.User{
ID: uuid.NewString(),
Name: "John Doe",
Email: "[email protected]",
Expand All @@ -37,3 +48,148 @@ func TestModel_Insert(t *testing.T) {
})
}
}

func TestModel_ApplyInsert(t *testing.T) {
ctx := context.Background()

model := &user.User{
ID: uuid.NewString(),
Name: "John Doe",
Email: "[email protected]",
CreatedAt: spanner.CommitTimestamp,
UpdatedAt: spanner.CommitTimestamp,
}

type fields struct {
model *user.User
}
tests := []struct {
name string
fields fields
hooks []HookFunc
wantErr bool
want []*user.User
}{
{
name: "insert a new user into the database",
fields: fields{model: model},
want: []*user.User{
{
Name: "John Doe",
Email: "[email protected]",
},
},
},
{
name: "insert a new user into the database with a hook",
fields: fields{
model: model,
},
hooks: []HookFunc{
func(ctx context.Context) []*spanner.Mutation {
user := &user.User{
ID: uuid.NewString(),
Name: "Jane Doe",
Email: "[email protected]",
CreatedAt: spanner.CommitTimestamp,
UpdatedAt: spanner.CommitTimestamp,
}

return []*spanner.Mutation{
user.Insert(ctx),
}
},
},
want: []*user.User{
{
Name: "Jane Doe",
Email: "[email protected]",
},
{
Name: "John Doe",
Email: "[email protected]",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
spannerClient, cleanup := setupSpanner(ctx, t)
defer cleanup()

m := NewModel(tt.fields.model,
WithSpannerClientOption[*user.User](*spannerClient),
)

for _, h := range tt.hooks {
m.On(Insert, h)
}

if _, err := m.ApplyInsert(ctx); (err != nil) != tt.wantErr {
t.Errorf("Model.ApplyInsert() error = %v, wantErr %v", err, tt.wantErr)
}

got, err := user.ReadUser(ctx, spannerClient.Single(), spanner.AllKeys())
if err != nil {
t.Fatal(err)
}

sort.Slice(got, func(i, j int) bool {
return got[i].Name < got[j].Name
})

if diff := cmp.Diff(tt.want, got,
cmpopts.IgnoreFields(user.User{}, "ID", "CreatedAt", "UpdatedAt")); diff != "" {
t.Errorf("ApplyInsert() mismatch (-want +got):\n%s", diff)
}
})
}
}

func setupSpanner(ctx context.Context, t *testing.T) (*spanner.Client, func()) {
server, err := spannertest.NewServer(":0")
if err != nil {
t.Fatal(err)
}

conn, err := grpc.Dial(server.Addr, grpc.WithTransportCredentials(
insecure.NewCredentials(),
))
if err != nil {
t.Fatal(err)
}

spannerDatabaseClient, err := database.NewDatabaseAdminClient(ctx, option.WithGRPCConn(conn))
if err != nil {
t.Fatal(err)
}

ddl, err := os.ReadFile("internal/sql/ddl.sql")
if err != nil {
t.Fatal(err)
}

op, err := spannerDatabaseClient.UpdateDatabaseDdl(ctx, &databasepb.UpdateDatabaseDdlRequest{
Database: "my-db",
Statements: []string{string(ddl)},
})
if err != nil {
t.Fatal(err)
}

if err := op.Wait(ctx); err != nil {
t.Fatal(err)
}

db := "projects/my-project/instances/my-instance/databases/my-db"
spannerClient, err := spanner.NewClient(ctx, db, option.WithGRPCConn(conn))
if err != nil {
t.Fatal(err)
}

return spannerClient, func() {
spannerClient.Close()
spannerDatabaseClient.Close()
server.Close()
}
}

0 comments on commit f111700

Please sign in to comment.