From ff89bb5bbc7c5dca91130dda6aca821b21ba4185 Mon Sep 17 00:00:00 2001 From: Bowen Date: Fri, 27 Mar 2026 11:24:34 +0800 Subject: [PATCH 1/5] chore: [#930] add tests for migration --- AGENTS.md | 69 +++++++ CLAUDE.md | 1 + database/seeders/database_seeder.go | 10 +- tests/feature/migration_test.go | 267 ++++++++++++++++++++++++++++ 4 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c3bf76c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,69 @@ +## Project Overview + +Goravel Example (`github.com/goravel/example`) is the framework example package. It demonstrates how to use the Goravel framework to build a web application, including core architecture, common commands, and code rules. + +## Code Rules + +- Use `any` instead of `interface{}`. +- Never edit `mocks/` directly; run `go tool mockery` to regenerate. +- Follow standard Go formatting/naming; add comments where logic isn't self-evident. Go version is in go.mod. + +### Tests + +- Prefer `go test ` or `-run ` over `go test ./...` (slow). +- Use table-driven tests covering happy path, failure, and edge cases. +- Skip trivial getters/setters unless they contain non-trivial logic. +- Use `testify/assert` with `assert.*(t, *)` or `require.*(t, *)` directly, not `assert.New(t)`. +- Use `testify/suite` for related test groups, and use `s.*(*, *)` when asserting. +- Use the testify `EXPECT` method for mocks; avoid `mock.Anything`. +- Use `assert.AnError` if needed, and `assert.Equal` for error assertions. +- Assert full maps/structs/slices/arrays, not individual fields. +- Prefer direct value assertions over `mock.MatchedBy`; use it only for dynamically-generated args. +- Every mock must use `.Once()` or `.Times()`, only use `.Maybe()` when necessary; avoid no-op expectations. +- Name tests `Test_[Optional]`; use table style or sub-tests for multiple cases. +- Don't use `assert.*` with `if` statements; use `assert.*` directly for clarity and better failure messages. +- Use `t.Run()` for sub-tests when testing multiple cases for the same function, and use table-driven tests for multiple cases with similar setup/assertions. Avoid writing separate test functions for each case when they share common logic. +- The basic table-driven test pattern is: + +```go +func TestFunction(t *testing.T) { + // The name should start with `mock` to indicate it's a mocked function. + var ( + ctx context.Context + mockFunc *mocks.MockedInterface + ) + + beforeEach := func() { + mockFunc = mocks.NewMockedInterface(t) + } + + tests := []struct { + name string + input any + setup func() + expect any + expectError error + }{ + { + name: "should do something", + input: someInput, + setup: func() { + mockFunc.EXPECT().SomeMethod(someArgs).Return(someResult, nil).Once() + }, + expect: someResult, + expectError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + beforeEach() + tt.setup() + + result, err := FunctionUnderTest(tt.input) + assert.Equal(t, tt.expect, result) + assert.Equal(t, tt.expectError, err) + }) + } +} +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/database/seeders/database_seeder.go b/database/seeders/database_seeder.go index afce748..d5aafa0 100644 --- a/database/seeders/database_seeder.go +++ b/database/seeders/database_seeder.go @@ -1,5 +1,10 @@ package seeders +import ( + "goravel/app/facades" + "goravel/app/models" +) + type DatabaseSeeder struct { } @@ -10,5 +15,8 @@ func (s *DatabaseSeeder) Signature() string { // Run executes the seeder logic. func (s *DatabaseSeeder) Run() error { - return nil + return facades.Orm().Query().Create(&models.User{ + Name: "migration", + Mail: "migration@goravel.dev", + }) } diff --git a/tests/feature/migration_test.go b/tests/feature/migration_test.go index a31cf4f..4861e1e 100644 --- a/tests/feature/migration_test.go +++ b/tests/feature/migration_test.go @@ -1,14 +1,25 @@ package feature import ( + "io" + "os" + "path/filepath" + "regexp" + "strings" "testing" + "time" + "github.com/spf13/cast" + + "github.com/goravel/framework/support/color" + "github.com/goravel/framework/support/file" "github.com/goravel/mysql" "github.com/goravel/sqlite" "github.com/goravel/sqlserver" "github.com/stretchr/testify/suite" "goravel/app/facades" + "goravel/app/models" "goravel/tests" ) @@ -64,10 +75,189 @@ func (s *MigrationTestSuite) TestFirst_After() { s.Equal("mail", columns[0].Name) s.Equal("alias", columns[3].Name) } + func (s *MigrationTestSuite) TestMigrate() { s.True(facades.Schema().HasTable("users")) } +func (s *MigrationTestSuite) TestCommandMigrate() { + total, err := s.migrationCount() + s.Require().NoError(err) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:reset")) + + count, err := s.migrationCount() + s.NoError(err) + s.Zero(count) + s.False(facades.Schema().HasTable("users")) + + s.NoError(facades.Artisan().Call("--no-ansi migrate")) + + count, err = s.migrationCount() + s.NoError(err) + s.Equal(total, count) + + s.True(facades.Schema().HasTable("users")) + s.True(facades.Schema().HasTable("jobs")) + s.True(facades.Schema().HasTable("failed_jobs")) + s.True(s.columnExists("users", "mail")) +} + +func (s *MigrationTestSuite) TestCommandMigrateReset() { + s.True(facades.Schema().HasTable("users")) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:reset")) + + count, err := s.migrationCount() + s.NoError(err) + s.Zero(count) + + s.False(facades.Schema().HasTable("users")) + s.False(facades.Schema().HasTable("jobs")) + s.False(facades.Schema().HasTable("failed_jobs")) +} + +func (s *MigrationTestSuite) TestCommandMigrateRefresh() { + total, err := s.migrationCount() + s.Require().NoError(err) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:refresh")) + afterRefresh, err := s.migrationCount() + s.NoError(err) + s.Equal(total, afterRefresh) + s.True(facades.Schema().HasTable("users")) + s.True(s.columnExists("users", "mail")) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:refresh --step 1")) + afterStepRefresh, err := s.migrationCount() + s.NoError(err) + s.Equal(total-1, afterStepRefresh) + s.True(facades.Schema().HasTable("users")) + s.True(s.columnExists("users", "mail")) +} + +func (s *MigrationTestSuite) TestCommandMigrateFresh() { + total, err := s.migrationCount() + s.Require().NoError(err) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:fresh --seed --seeder DatabaseSeeder")) + + count, err := s.migrationCount() + s.NoError(err) + s.Equal(total, count) + + s.True(facades.Schema().HasTable("users")) + s.True(facades.Schema().HasTable("jobs")) + s.True(facades.Schema().HasTable("failed_jobs")) + s.True(s.columnExists("users", "mail")) + + var user models.User + s.NoError(facades.Orm().Query().Where("mail", "migration@goravel.dev").FirstOrFail(&user)) + s.Equal("migration", user.Name) +} + +func (s *MigrationTestSuite) TestCommandMigrateRollback() { + total, err := s.migrationCount() + s.Require().NoError(err) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:rollback")) + afterDefaultRollback, err := s.migrationCount() + s.NoError(err) + s.Zero(afterDefaultRollback) + + s.RefreshDatabase() + + s.NoError(facades.Artisan().Call("--no-ansi migrate:rollback --step 1")) + afterStepRollback, err := s.migrationCount() + s.NoError(err) + s.Equal(total-1, afterStepRollback) + + s.RefreshDatabase() + + s.NoError(facades.Artisan().Call("--no-ansi migrate:rollback --step 1")) + s.NoError(facades.Artisan().Call("--no-ansi migrate")) + + latestBatch, err := s.latestMigrationBatch() + s.NoError(err) + s.Equal(2, latestBatch) + + s.NoError(facades.Artisan().Call("--no-ansi migrate:rollback --batch " + cast.ToString(latestBatch))) + afterBatchRollback, err := s.migrationCount() + s.NoError(err) + s.Equal(total-1, afterBatchRollback) +} + +func (s *MigrationTestSuite) TestCommandMigrateStatus() { + ranOutput := s.captureArtisanOutput("--no-ansi migrate:status") + s.Contains(ranOutput, "Migration name") + s.Contains(ranOutput, "Batch / Status") + s.Contains(ranOutput, "20210101000001_create_users_table") + s.Contains(ranOutput, "20210101000002_create_jobs_table") + s.Contains(ranOutput, "20250331111908_add_columns_to_users_table") + s.Contains(ranOutput, "20250331093125_alert_columns_of_users_table") + s.Contains(ranOutput, "Ran") + + s.NoError(facades.Artisan().Call("--no-ansi migrate:reset")) + + pendingOutput := s.captureArtisanOutput("--no-ansi migrate:status") + s.Contains(pendingOutput, "Migration name") + s.Contains(pendingOutput, "Batch / Status") + s.Contains(pendingOutput, "20210101000001_create_users_table") + s.Contains(pendingOutput, "20210101000002_create_jobs_table") + s.Contains(pendingOutput, "20250331111908_add_columns_to_users_table") + s.Contains(pendingOutput, "20250331093125_alert_columns_of_users_table") + s.Contains(pendingOutput, "Pending") +} + +func (s *MigrationTestSuite) TestCommandMakeMigration() { + root := s.projectRoot() + snapshotAndRestoreBootstrapMigrations(s.T(), root) + + wd, err := os.Getwd() + s.Require().NoError(err) + s.Require().NoError(os.Chdir(root)) + s.T().Cleanup(func() { + s.NoError(os.Chdir(wd)) + }) + + driver := facades.Orm().Config().Driver + migrationName := "test_" + driver + "_" + cast.ToString(time.Now().UnixNano()) + beforeFiles := s.listMigrationFiles(root) + + s.NoError(facades.Artisan().Call("--no-ansi make:migration " + migrationName)) + + afterFiles := s.listMigrationFiles(root) + var createdFiles []string + for item := range afterFiles { + if _, ok := beforeFiles[item]; !ok { + createdFiles = append(createdFiles, item) + } + } + + s.Require().NotEmpty(createdFiles) + migrationPath := filepath.Join(root, "database", "migrations", createdFiles[0]) + s.Require().FileExists(migrationPath) + + s.T().Cleanup(func() { + if migrationPath != "" { + s.NoError(file.Remove(migrationPath)) + } + }) + + content, err := os.ReadFile(migrationPath) + s.Require().NoError(err) + + re := regexp.MustCompile(`type\s+(M[^\s]+)\s+struct`) + matches := re.FindStringSubmatch(string(content)) + s.Require().Len(matches, 2) + structName := matches[1] + + bootstrapContent, err := os.ReadFile(filepath.Join(root, "bootstrap", "migrations.go")) + s.Require().NoError(err) + updatedBootstrap := string(bootstrapContent) + s.Contains(updatedBootstrap, "&migrations."+structName+"{}") +} + func (s *MigrationTestSuite) TestTableComment() { if facades.Schema().Orm().Config().Driver == sqlite.Name || facades.Schema().Orm().Config().Driver == sqlserver.Name { s.T().Skip("sqlite and sqlserver does not support table comment") @@ -82,3 +272,80 @@ func (s *MigrationTestSuite) TestTableComment() { } } } + +func (s *MigrationTestSuite) migrationCount() (int64, error) { + table := facades.Config().GetString("database.migrations.table") + return facades.DB().Table(table).Count() +} + +func (s *MigrationTestSuite) latestMigrationBatch() (int, error) { + table := facades.Config().GetString("database.migrations.table") + + var batch int + err := facades.DB().Table(table).OrderByDesc("batch").Limit(1).Pluck("batch", &batch) + if err != nil { + return 0, err + } + + return batch, nil +} + +func (s *MigrationTestSuite) columnExists(table, column string) bool { + return facades.Schema().HasColumn(table, column) +} + +func (s *MigrationTestSuite) captureArtisanOutput(command string) string { + return color.CaptureOutput(func(_ io.Writer) { + s.NoError(facades.Artisan().Call(command)) + }) +} + +func (s *MigrationTestSuite) listMigrationFiles(root string) map[string]struct{} { + migrationDir := filepath.Join(root, "database", "migrations") + entries, err := os.ReadDir(migrationDir) + s.NoError(err) + + files := make(map[string]struct{}) + for _, entry := range entries { + if entry.IsDir() { + continue + } + if strings.HasSuffix(entry.Name(), ".go") { + files[entry.Name()] = struct{}{} + } + } + + return files +} + +func (s *MigrationTestSuite) projectRoot() string { + dir, err := os.Getwd() + s.Require().NoError(err) + + for { + if file.Exists(filepath.Join(dir, "go.mod")) && file.Exists(filepath.Join(dir, "bootstrap", "migrations.go")) { + return dir + } + + parent := filepath.Dir(dir) + if parent == dir { + s.T().Fatal("cannot locate project root from current working directory") + } + + dir = parent + } +} + +func snapshotAndRestoreBootstrapMigrations(t *testing.T, root string) { + path := filepath.Join(root, "bootstrap", "migrations.go") + original, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s failed: %v", path, err) + } + + t.Cleanup(func() { + if err := os.WriteFile(path, original, 0o644); err != nil { + t.Fatalf("restore %s failed: %v", path, err) + } + }) +} From 0f0945b332fe5c3b25e8236cc14463a81480c963 Mon Sep 17 00:00:00 2001 From: Bowen Date: Sun, 29 Mar 2026 17:07:52 +0800 Subject: [PATCH 2/5] fix: use path helpers and fix migration Down methods in tests --- ...250330911908_add_columns_to_users_table.go | 6 +- ...0331093125_alert_columns_of_users_table.go | 6 ++ go.mod | 6 +- go.sum | 8 +- tests/feature/http_test.go | 4 - tests/feature/migration_test.go | 82 +++++++------------ 6 files changed, 46 insertions(+), 66 deletions(-) diff --git a/database/migrations/20250330911908_add_columns_to_users_table.go b/database/migrations/20250330911908_add_columns_to_users_table.go index 22c0881..9d9678c 100755 --- a/database/migrations/20250330911908_add_columns_to_users_table.go +++ b/database/migrations/20250330911908_add_columns_to_users_table.go @@ -10,7 +10,7 @@ type M20250330911908AddColumnsToUsersTable struct{} // Signature The unique signature for the migration. func (r *M20250330911908AddColumnsToUsersTable) Signature() string { - return "20250331111908_add_columns_to_users_table" + return "20250330911908_add_columns_to_users_table" } // Up Run the migrations. @@ -23,5 +23,7 @@ func (r *M20250330911908AddColumnsToUsersTable) Up() error { // Down Reverse the migrations. func (r *M20250330911908AddColumnsToUsersTable) Down() error { - return nil + return facades.Schema().Table("users", func(table schema.Blueprint) { + table.DropColumn("alias", "email") + }) } diff --git a/database/migrations/20250331093125_alert_columns_of_users_table.go b/database/migrations/20250331093125_alert_columns_of_users_table.go index 8b7ee54..8afc3ab 100755 --- a/database/migrations/20250331093125_alert_columns_of_users_table.go +++ b/database/migrations/20250331093125_alert_columns_of_users_table.go @@ -27,5 +27,11 @@ func (r *M20250331093125AlertColumnsOfUsersTable) Up() error { // Down Reverse the migrations. func (r *M20250331093125AlertColumnsOfUsersTable) Down() error { + if facades.Schema().HasTable("users") { + return facades.Schema().Table("users", func(table schema.Blueprint) { + table.RenameColumn("mail", "email") + }) + } + return nil } diff --git a/go.mod b/go.mod index ed94133..7c5766d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/goravel/cos v1.17.0 github.com/goravel/example-proto v0.0.1 github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b - github.com/goravel/framework v1.17.2-0.20260322042944-d61a6cc1601e + github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66 github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 github.com/goravel/minio v1.17.0 github.com/goravel/mysql v1.17.0 @@ -31,7 +31,7 @@ require ( github.com/vektah/gqlparser/v2 v2.5.19 go.opentelemetry.io/otel v1.42.0 go.opentelemetry.io/otel/metric v1.42.0 - google.golang.org/grpc v1.79.2 + google.golang.org/grpc v1.79.3 ) require ( @@ -240,4 +240,4 @@ require ( gorm.io/plugin/dbresolver v1.6.2 // indirect ) -replace github.com/goravel/framework => github.com/goravel/framework v1.17.2-0.20260322042944-d61a6cc1601e +replace github.com/goravel/framework => github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66 diff --git a/go.sum b/go.sum index 09c8869..8c823cf 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,8 @@ github.com/goravel/example-proto v0.0.1 h1:ZxETeKREQWjuJ49bX/Hqj1NLR5Vyj489Ks6dR github.com/goravel/example-proto v0.0.1/go.mod h1:I8IPsHr4Ndf7KxmdsRpBR2LQ0Geo48+pjv9IIWf3mZg= github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b h1:peMAbfUTyQJCtA4wABmOowETE8N5v6i0GEy3vqVWeCg= github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b/go.mod h1:vVfU2LnxhCXOE1QH9U0/bFPBfIvbqCD3xyDkSm4wmVM= -github.com/goravel/framework v1.17.2-0.20260322042944-d61a6cc1601e h1:WXtXYc9lWIbpkbnGiPH4GtWopNh5KZaPEk/wipWPvDI= -github.com/goravel/framework v1.17.2-0.20260322042944-d61a6cc1601e/go.mod h1:2/eF2HWF3MFE1AIVnY5e+zebNCsawnMJRT14NzSiq/I= +github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66 h1:b5FFDlVunxxalmqmqvkLcGip2ji1RYpsyByKKvv3EhQ= +github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66/go.mod h1:MksvSJ0GQTfVgIIbAqOPs7S3hQGtVCCfWhZSQNVSsBA= github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 h1:P1wUP46zVOoD3wXFrydvoBs3q8SfJO7Gk7TU6emKNZE= github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889/go.mod h1:GsI8Ep1tfePcLHSx0vVtj+k3PLwpn/rUs4tKDFaH7b0= github.com/goravel/minio v1.17.0 h1:WGiPP/KZl/fuDpT9THRM83wjhLCqe1oIAyNVJvVjhS4= @@ -772,8 +772,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= -google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/feature/http_test.go b/tests/feature/http_test.go index b375937..6c9e9b7 100644 --- a/tests/feature/http_test.go +++ b/tests/feature/http_test.go @@ -6,7 +6,6 @@ import ( "testing" contractshttp "github.com/goravel/framework/contracts/http" - "github.com/goravel/framework/support" "github.com/goravel/framework/support/http" "github.com/stretchr/testify/suite" @@ -103,9 +102,6 @@ func (s *HttpTestSuite) TestInputMapArray() { } func (s *HttpTestSuite) TestLang() { - // Change working directory to project root to use current lang files - s.T().Chdir(support.RelativePath) - tests := []struct { name string lang string diff --git a/tests/feature/migration_test.go b/tests/feature/migration_test.go index 4861e1e..b00bfbc 100644 --- a/tests/feature/migration_test.go +++ b/tests/feature/migration_test.go @@ -3,7 +3,6 @@ package feature import ( "io" "os" - "path/filepath" "regexp" "strings" "testing" @@ -13,6 +12,7 @@ import ( "github.com/goravel/framework/support/color" "github.com/goravel/framework/support/file" + "github.com/goravel/framework/support/path" "github.com/goravel/mysql" "github.com/goravel/sqlite" "github.com/goravel/sqlserver" @@ -129,9 +129,15 @@ func (s *MigrationTestSuite) TestCommandMigrateRefresh() { s.True(s.columnExists("users", "mail")) s.NoError(facades.Artisan().Call("--no-ansi migrate:refresh --step 1")) + afterStepRefresh, err := s.migrationCount() s.NoError(err) - s.Equal(total-1, afterStepRefresh) + s.Equal(total, afterStepRefresh) + + lastBatch, err := s.latestMigrationBatch() + s.NoError(err) + s.Equal(lastBatch, 2) + s.True(facades.Schema().HasTable("users")) s.True(s.columnExists("users", "mail")) } @@ -193,7 +199,7 @@ func (s *MigrationTestSuite) TestCommandMigrateStatus() { s.Contains(ranOutput, "Batch / Status") s.Contains(ranOutput, "20210101000001_create_users_table") s.Contains(ranOutput, "20210101000002_create_jobs_table") - s.Contains(ranOutput, "20250331111908_add_columns_to_users_table") + s.Contains(ranOutput, "20250330911908_add_columns_to_users_table") s.Contains(ranOutput, "20250331093125_alert_columns_of_users_table") s.Contains(ranOutput, "Ran") @@ -204,29 +210,31 @@ func (s *MigrationTestSuite) TestCommandMigrateStatus() { s.Contains(pendingOutput, "Batch / Status") s.Contains(pendingOutput, "20210101000001_create_users_table") s.Contains(pendingOutput, "20210101000002_create_jobs_table") - s.Contains(pendingOutput, "20250331111908_add_columns_to_users_table") + s.Contains(pendingOutput, "20250330911908_add_columns_to_users_table") s.Contains(pendingOutput, "20250331093125_alert_columns_of_users_table") s.Contains(pendingOutput, "Pending") } func (s *MigrationTestSuite) TestCommandMakeMigration() { - root := s.projectRoot() - snapshotAndRestoreBootstrapMigrations(s.T(), root) + migrationsPath := path.Bootstrap("migrations.go") + originalContent, err := os.ReadFile(migrationsPath) + if err != nil { + s.T().Fatalf("read %s failed: %v", migrationsPath, err) + } - wd, err := os.Getwd() - s.Require().NoError(err) - s.Require().NoError(os.Chdir(root)) s.T().Cleanup(func() { - s.NoError(os.Chdir(wd)) + if err := os.WriteFile(migrationsPath, originalContent, 0o644); err != nil { + s.T().Fatalf("restore %s failed: %v", migrationsPath, err) + } }) + beforeFiles := s.listMigrationFiles() + driver := facades.Orm().Config().Driver migrationName := "test_" + driver + "_" + cast.ToString(time.Now().UnixNano()) - beforeFiles := s.listMigrationFiles(root) - s.NoError(facades.Artisan().Call("--no-ansi make:migration " + migrationName)) - afterFiles := s.listMigrationFiles(root) + afterFiles := s.listMigrationFiles() var createdFiles []string for item := range afterFiles { if _, ok := beforeFiles[item]; !ok { @@ -235,7 +243,8 @@ func (s *MigrationTestSuite) TestCommandMakeMigration() { } s.Require().NotEmpty(createdFiles) - migrationPath := filepath.Join(root, "database", "migrations", createdFiles[0]) + + migrationPath := path.Migration(createdFiles[0]) s.Require().FileExists(migrationPath) s.T().Cleanup(func() { @@ -250,12 +259,11 @@ func (s *MigrationTestSuite) TestCommandMakeMigration() { re := regexp.MustCompile(`type\s+(M[^\s]+)\s+struct`) matches := re.FindStringSubmatch(string(content)) s.Require().Len(matches, 2) - structName := matches[1] - bootstrapContent, err := os.ReadFile(filepath.Join(root, "bootstrap", "migrations.go")) + structName := matches[1] + updatedBootstrap, err := os.ReadFile(migrationsPath) s.Require().NoError(err) - updatedBootstrap := string(bootstrapContent) - s.Contains(updatedBootstrap, "&migrations."+structName+"{}") + s.Contains(string(updatedBootstrap), "&migrations."+structName+"{}") } func (s *MigrationTestSuite) TestTableComment() { @@ -282,7 +290,7 @@ func (s *MigrationTestSuite) latestMigrationBatch() (int, error) { table := facades.Config().GetString("database.migrations.table") var batch int - err := facades.DB().Table(table).OrderByDesc("batch").Limit(1).Pluck("batch", &batch) + err := facades.DB().Table(table).OrderByDesc("batch").Limit(1).Value("batch", &batch) if err != nil { return 0, err } @@ -300,8 +308,8 @@ func (s *MigrationTestSuite) captureArtisanOutput(command string) string { }) } -func (s *MigrationTestSuite) listMigrationFiles(root string) map[string]struct{} { - migrationDir := filepath.Join(root, "database", "migrations") +func (s *MigrationTestSuite) listMigrationFiles() map[string]struct{} { + migrationDir := path.Migration() entries, err := os.ReadDir(migrationDir) s.NoError(err) @@ -317,35 +325,3 @@ func (s *MigrationTestSuite) listMigrationFiles(root string) map[string]struct{} return files } - -func (s *MigrationTestSuite) projectRoot() string { - dir, err := os.Getwd() - s.Require().NoError(err) - - for { - if file.Exists(filepath.Join(dir, "go.mod")) && file.Exists(filepath.Join(dir, "bootstrap", "migrations.go")) { - return dir - } - - parent := filepath.Dir(dir) - if parent == dir { - s.T().Fatal("cannot locate project root from current working directory") - } - - dir = parent - } -} - -func snapshotAndRestoreBootstrapMigrations(t *testing.T, root string) { - path := filepath.Join(root, "bootstrap", "migrations.go") - original, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s failed: %v", path, err) - } - - t.Cleanup(func() { - if err := os.WriteFile(path, original, 0o644); err != nil { - t.Fatalf("restore %s failed: %v", path, err) - } - }) -} From 9d067a9a26343d2fa2047f7b459b564a3932ded4 Mon Sep 17 00:00:00 2001 From: Bowen Date: Sun, 29 Mar 2026 17:35:35 +0800 Subject: [PATCH 3/5] optimize --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 7c5766d..6a6a62c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/goravel/cos v1.17.0 github.com/goravel/example-proto v0.0.1 github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b - github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66 + github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 github.com/goravel/minio v1.17.0 github.com/goravel/mysql v1.17.0 @@ -240,4 +240,4 @@ require ( gorm.io/plugin/dbresolver v1.6.2 // indirect ) -replace github.com/goravel/framework => github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66 +replace github.com/goravel/framework => github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa diff --git a/go.sum b/go.sum index 8c823cf..94c30ed 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,8 @@ github.com/goravel/example-proto v0.0.1 h1:ZxETeKREQWjuJ49bX/Hqj1NLR5Vyj489Ks6dR github.com/goravel/example-proto v0.0.1/go.mod h1:I8IPsHr4Ndf7KxmdsRpBR2LQ0Geo48+pjv9IIWf3mZg= github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b h1:peMAbfUTyQJCtA4wABmOowETE8N5v6i0GEy3vqVWeCg= github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b/go.mod h1:vVfU2LnxhCXOE1QH9U0/bFPBfIvbqCD3xyDkSm4wmVM= -github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66 h1:b5FFDlVunxxalmqmqvkLcGip2ji1RYpsyByKKvv3EhQ= -github.com/goravel/framework v1.17.2-0.20260329082340-b250edc75e66/go.mod h1:MksvSJ0GQTfVgIIbAqOPs7S3hQGtVCCfWhZSQNVSsBA= +github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa h1:J4pK2lyIOmUre/YszgyLbkOXqm1t2+mU4HqTv6X64gA= +github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa/go.mod h1:MksvSJ0GQTfVgIIbAqOPs7S3hQGtVCCfWhZSQNVSsBA= github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 h1:P1wUP46zVOoD3wXFrydvoBs3q8SfJO7Gk7TU6emKNZE= github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889/go.mod h1:GsI8Ep1tfePcLHSx0vVtj+k3PLwpn/rUs4tKDFaH7b0= github.com/goravel/minio v1.17.0 h1:WGiPP/KZl/fuDpT9THRM83wjhLCqe1oIAyNVJvVjhS4= From 437e255500bd330ebb7a04d533071a25ea9fe15c Mon Sep 17 00:00:00 2001 From: Bowen Date: Sun, 29 Mar 2026 17:52:50 +0800 Subject: [PATCH 4/5] optimize --- go.mod | 4 ++-- go.sum | 4 ++-- package_test.go | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 6a6a62c..8875ec7 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/goravel/cos v1.17.0 github.com/goravel/example-proto v0.0.1 github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b - github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa + github.com/goravel/framework v1.17.2-0.20260329094602-be6d0d1dcf97 github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 github.com/goravel/minio v1.17.0 github.com/goravel/mysql v1.17.0 @@ -240,4 +240,4 @@ require ( gorm.io/plugin/dbresolver v1.6.2 // indirect ) -replace github.com/goravel/framework => github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa +replace github.com/goravel/framework => github.com/goravel/framework v1.17.2-0.20260329094602-be6d0d1dcf97 diff --git a/go.sum b/go.sum index 94c30ed..f1f72ed 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,8 @@ github.com/goravel/example-proto v0.0.1 h1:ZxETeKREQWjuJ49bX/Hqj1NLR5Vyj489Ks6dR github.com/goravel/example-proto v0.0.1/go.mod h1:I8IPsHr4Ndf7KxmdsRpBR2LQ0Geo48+pjv9IIWf3mZg= github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b h1:peMAbfUTyQJCtA4wABmOowETE8N5v6i0GEy3vqVWeCg= github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b/go.mod h1:vVfU2LnxhCXOE1QH9U0/bFPBfIvbqCD3xyDkSm4wmVM= -github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa h1:J4pK2lyIOmUre/YszgyLbkOXqm1t2+mU4HqTv6X64gA= -github.com/goravel/framework v1.17.2-0.20260329093411-aab3d970acfa/go.mod h1:MksvSJ0GQTfVgIIbAqOPs7S3hQGtVCCfWhZSQNVSsBA= +github.com/goravel/framework v1.17.2-0.20260329094602-be6d0d1dcf97 h1:fD5VV8KfIGduf1bBigFCG3DGgdTUfz1Ek83c7cwcLaA= +github.com/goravel/framework v1.17.2-0.20260329094602-be6d0d1dcf97/go.mod h1:MksvSJ0GQTfVgIIbAqOPs7S3hQGtVCCfWhZSQNVSsBA= github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 h1:P1wUP46zVOoD3wXFrydvoBs3q8SfJO7Gk7TU6emKNZE= github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889/go.mod h1:GsI8Ep1tfePcLHSx0vVtj+k3PLwpn/rUs4tKDFaH7b0= github.com/goravel/minio v1.17.0 h1:WGiPP/KZl/fuDpT9THRM83wjhLCqe1oIAyNVJvVjhS4= diff --git a/package_test.go b/package_test.go index d52a30b..9f18e84 100644 --- a/package_test.go +++ b/package_test.go @@ -163,7 +163,9 @@ func TestInstallAndUninstallLocalPackage(t *testing.T) { assert.True(t, file.Exists(path.Base("packages", "example"))) assert.True(t, file.Exists(path.Base("packages", "example", "setup", "setup.go"))) - assert.False(t, facades.Process().Run("go run . artisan package:install goravel/packages/example").Failed()) + result := facades.Process().Run("go run . artisan package:install goravel/packages/example") + assert.NoError(t, result.Error()) + assert.False(t, result.Failed()) assert.True(t, file.Contain(path.Bootstrap("providers.go"), "&example.ServiceProvider{},")) assert.True(t, file.Contain(path.Bootstrap("providers.go"), "goravel/packages/example")) From cc951078a912430c0a8c876171fbae146b28d588 Mon Sep 17 00:00:00 2001 From: Bowen Date: Sun, 29 Mar 2026 22:35:01 +0800 Subject: [PATCH 5/5] optimize --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8875ec7..3887ee6 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/goravel/cos v1.17.0 github.com/goravel/example-proto v0.0.1 github.com/goravel/fiber v1.17.1-0.20260319150449-0a18b9c6e22b - github.com/goravel/framework v1.17.2-0.20260329094602-be6d0d1dcf97 + github.com/goravel/framework v1.17.2-0.20260329143353-aa89cf5921cb github.com/goravel/gin v1.17.1-0.20260319150458-6d1543fdf889 github.com/goravel/minio v1.17.0 github.com/goravel/mysql v1.17.0