From e83469acab32584d49f44abb047e99aa0b31a4d2 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Wed, 10 Aug 2022 11:19:46 +0200 Subject: [PATCH 1/6] Improve README file --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index 3a2e811..f6c2af0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,61 @@ # Foundation A set of helpers to help to create and manage database migration tests. + +## Install + +``` +go get git.ctrlz.es/mgdelacroix/foundation +``` + +## Usage + +To start using foundation, you need to implement the `Migrator` +interface, describing how your tool manages migrations and what are +the intermediate steps (generally data migrations), if any, that need +to run at the end of each migration step: + +```go +type Migrator interface { + DB() *sql.DB + DriverName() string + Setup() error + MigrateToStep(step int) error + TearDown() error +} + +interceptors := map[int]Interceptor{ + // function that will run after step 6 + 6: func() err { + return myStore.RunDataMigration() + }, +} +``` + +With the interface implemented, you can use `foundation` in your tests +to load fixtures, set the database on a specific state and then run +your assertions: + +```go +t.Run("migration should link book 1 with its author", func(t *testing.T) { + f := foundation.New(t, migrator). + RegisterInterceptors(interceptors). + // runs migrations up to and including 5 + MigrateToStep(5). + // loads the SQL of the file + ExecFile("./myfixtures.sql"). + // runs migration 6 and its interceptor function + MigrateToStep(6) + defer f.TearDown() + + book := struct{ + ID int + AuthorID int + }{} + + err = f.DB().Get(&book, "SELECT id, authorID FROM books") + require.NoError(t, err) + require.Equal(t, 1, book.ID) + require.Equal(t, 3, book.AuthorID) +}) +``` From 1f7bb6520535a93729acb74db54661acd55d46cc Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Wed, 10 Aug 2022 13:31:00 +0200 Subject: [PATCH 2/6] Simplify for loop --- foundation.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/foundation.go b/foundation.go index ced5b81..7520850 100644 --- a/foundation.go +++ b/foundation.go @@ -72,16 +72,12 @@ func (f *Foundation) calculateNextStep(step int) int { } i := f.currentStep - for { + for i < step { i++ if _, ok := f.interceptors[i]; ok { break } - - if step == i { - break - } } return i From e399d1e547e1b3b8fb078d6a839b05cd0303cc5f Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Thu, 11 Aug 2022 13:24:23 +0200 Subject: [PATCH 3/6] Update foundation's package --- foundation.go | 2 +- foundation_test.go | 2 +- helpers_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/foundation.go b/foundation.go index 7520850..427d06d 100644 --- a/foundation.go +++ b/foundation.go @@ -1,4 +1,4 @@ -package main +package foundation import ( "database/sql" diff --git a/foundation_test.go b/foundation_test.go index 6e825b7..3cd7c8b 100644 --- a/foundation_test.go +++ b/foundation_test.go @@ -1,4 +1,4 @@ -package main +package foundation import ( "testing" diff --git a/helpers_test.go b/helpers_test.go index d1a9d47..4430949 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,4 +1,4 @@ -package main +package foundation import "database/sql" From a2938fffcee4f036932e6ca42f6016bfeac3b687 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Thu, 11 Aug 2022 19:13:42 +0200 Subject: [PATCH 4/6] Migrate to github --- README.md | 4 ++-- go.mod | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f6c2af0..79d03a7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Foundation -A set of helpers to help to create and manage database migration tests. +A framework to write simple database migration tests. ## Install ``` -go get git.ctrlz.es/mgdelacroix/foundation +go get github.com/mgdelacroix/foundation ``` ## Usage diff --git a/go.mod b/go.mod index 878bd52..b1c9fb4 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.ctrlz.es/mgdelacroix/foundation +module github.com/mgdelacroix/foundation go 1.18 From 0bfc18f7353837b389a19dd7356e1e1a56fde4c1 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Fri, 12 Aug 2022 16:34:23 +0200 Subject: [PATCH 5/6] Add RunInterceptor test helper and update MigrateToStep ones --- foundation.go | 59 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/foundation.go b/foundation.go index 427d06d..1587bd7 100644 --- a/foundation.go +++ b/foundation.go @@ -22,6 +22,7 @@ type Migrator interface { DriverName() string Setup() error MigrateToStep(step int) error + Interceptors() map[int]Interceptor TearDown() error } @@ -40,12 +41,15 @@ func New(t *testing.T, migrator Migrator) *Foundation { currentStep: 0, // if true, will run the migrator Step function once per step // instead of just once with the final step - stepByStep: false, - migrator: migrator, - db: db, + stepByStep: false, + migrator: migrator, + interceptors: migrator.Interceptors(), + db: db, } } +// RegisterInterceptors replaced the migrator interceptors with new +// ones, in case we want to check a special case for a given test func (f *Foundation) RegisterInterceptors(interceptors map[int]Interceptor) *Foundation { f.interceptors = interceptors return f @@ -83,7 +87,7 @@ func (f *Foundation) calculateNextStep(step int) int { return i } -func (f *Foundation) MigrateToStep(step int) *Foundation { +func (f *Foundation) migrateToStep(step int, skipLastInterceptor bool) *Foundation { if step == f.currentStep { // log nothing to do return f @@ -95,7 +99,7 @@ func (f *Foundation) MigrateToStep(step int) *Foundation { // if there are no interceptors, just migrate to the last step if f.interceptors == nil { - if err := f.migrateToStep(step); err != nil { + if err := f.doMigrateToStep(step); err != nil { f.t.Fatalf("migration to step %d failed: %s", step, err) } @@ -105,14 +109,20 @@ func (f *Foundation) MigrateToStep(step int) *Foundation { for f.currentStep < step { nextStep := f.calculateNextStep(step) - if err := f.migrateToStep(nextStep); err != nil { + if err := f.doMigrateToStep(nextStep); err != nil { f.t.Fatalf("migration to step %d failed: %s", nextStep, err) } + // if we want to skip the last interceptor and we're in the + // last step, just continue + if skipLastInterceptor && nextStep == step { + continue + } + interceptorFn, ok := f.interceptors[nextStep] if ok { if err := interceptorFn(); err != nil { - f.t.Fatalf("interceptor function for step %d failed", nextStep) + f.t.Fatalf("interceptor function for step %d failed: %s", nextStep, err) } } } @@ -120,11 +130,42 @@ func (f *Foundation) MigrateToStep(step int) *Foundation { return f } -// migrateToStep executes the migrator function to migrate to a +// MigrateToStep instructs the migrator to move forward until step is +// reached. While migrating, it will run the interceptors after the +// step they're defined for +func (f *Foundation) MigrateToStep(step int) *Foundation { + return f.migrateToStep(step, false) +} + +// MigrateToStepSkippingLastInterceptor instructs the migrator to move +// forward until step is reached, skipping the last interceptor. This +// is useful if we want to load fixtures on the last step but before +// running the interceptor code, so we can check how that data is +// modified by the interceptor +func (f *Foundation) MigrateToStepSkippingLastInterceptor(step int) *Foundation { + return f.migrateToStep(step, true) +} + +// RunInterceptor executes the code of the interceptor corresponding +// to step +func (f *Foundation) RunInterceptor(step int) *Foundation { + interceptorFn, ok := f.interceptors[step] + if !ok { + f.t.Fatalf("no interceptor found for step %d", step) + } + + if err := interceptorFn(); err != nil { + f.t.Fatalf("interceptor function for step %d failed: %s", step, err) + } + + return f +} + +// doMigrateToStep executes the migrator function to migrate to a // specific step and updates the foundation currentStep to reflect the // result. This function doesn't take into account interceptors, that // happens on MigrateToStep -func (f *Foundation) migrateToStep(step int) error { +func (f *Foundation) doMigrateToStep(step int) error { if f.stepByStep { for f.currentStep < step { if err := f.migrator.MigrateToStep(f.currentStep + 1); err != nil { From 29ac8f6f33a3de34ff906b0b46954a112389956a Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Fri, 12 Aug 2022 16:55:21 +0200 Subject: [PATCH 6/6] Update README with the new library API --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 79d03a7..3f93520 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ type Migrator interface { DriverName() string Setup() error MigrateToStep(step int) error + Interceptors() map[int]Interceptor TearDown() error } @@ -39,7 +40,6 @@ your assertions: ```go t.Run("migration should link book 1 with its author", func(t *testing.T) { f := foundation.New(t, migrator). - RegisterInterceptors(interceptors). // runs migrations up to and including 5 MigrateToStep(5). // loads the SQL of the file @@ -48,14 +48,21 @@ t.Run("migration should link book 1 with its author", func(t *testing.T) { MigrateToStep(6) defer f.TearDown() - book := struct{ - ID int - AuthorID int - }{} + book := struct{ID int; AuthorID int}{} - err = f.DB().Get(&book, "SELECT id, authorID FROM books") + err := f.DB().Get(&book, "SELECT id, authorID FROM books") require.NoError(t, err) require.Equal(t, 1, book.ID) require.Equal(t, 3, book.AuthorID) }) + +t.Run("test specifically the interceptor 6", func(t *testing.T) { + f := foundation.New(t, migrator). + MigrateToStepSkippingLastInterceptor(6). + ExecFile("./myfixtures.sql"). + RunInterceptor(6) + defer f.TearDown() + + // ... +}) ```