diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go deleted file mode 100644 index a36d05c76e0..00000000000 --- a/cmd/cmd_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package cmd - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v3" -) - -func TestDefaultCommand(t *testing.T) { - test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) { - called := false - cmd := &cli.Command{ - DefaultCommand: "test", - Commands: []*cli.Command{ - { - Name: "test", - Action: func(ctx context.Context, command *cli.Command) error { - retName, retValid := isValidDefaultSubCommand(command) - assert.Equal(t, expectedRetName, retName) - assert.Equal(t, expectedRetValid, retValid) - called = true - return nil - }, - }, - }, - } - assert.NoError(t, cmd.Run(t.Context(), args)) - assert.True(t, called) - } - test(t, []string{"./gitea"}, "", true) - test(t, []string{"./gitea", "test"}, "", true) - test(t, []string{"./gitea", "other"}, "other", false) -} diff --git a/cmd/cmdtest/cmd_test.go b/cmd/cmdtest/cmd_test.go new file mode 100644 index 00000000000..4ff854f8abf --- /dev/null +++ b/cmd/cmdtest/cmd_test.go @@ -0,0 +1,237 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +// Tests here reload the config system multiple times with uncontrollable details. +// So they must be in a separate package, to avoid affecting other tests + +package cmdtest + +import ( + "context" + "errors" + "fmt" + "io" + "path/filepath" + "strings" + "testing" + + "code.gitea.io/gitea/cmd" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} + +func makePathOutput(workPath, customPath, customConf string) string { + return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) +} + +func newTestApp(testCmd cli.Command) *cli.Command { + app := cmd.NewMainApp(cmd.AppVersion{}) + testCmd.Name = util.IfZero(testCmd.Name, "test-cmd") + cmd.PrepareSubcommandWithGlobalFlags(&testCmd) + app.Commands = append(app.Commands, &testCmd) + app.DefaultCommand = testCmd.Name + return app +} + +type runResult struct { + Stdout string + Stderr string + ExitCode int +} + +func runTestApp(app *cli.Command, args ...string) (runResult, error) { + outBuf := new(strings.Builder) + errBuf := new(strings.Builder) + app.Writer = outBuf + app.ErrWriter = errBuf + exitCode := -1 + defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)() + defer test.MockVariableValue(&cli.OsExiter, func(code int) { + if exitCode == -1 { + exitCode = code // save the exit code once and then reset the writer (to simulate the exit) + app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard + } + })() + err := cmd.RunMainApp(app, args...) + return runResult{outBuf.String(), errBuf.String(), exitCode}, err +} + +func TestCliCmd(t *testing.T) { + defaultWorkPath := filepath.FromSlash("/tmp/mocked-work-path") + defaultCustomPath := filepath.Join(defaultWorkPath, "custom") + defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") + defer setting.MockBuiltinPaths(defaultWorkPath, "", "")() + + cli.CommandHelpTemplate = "(command help template)" + cli.RootCommandHelpTemplate = "(app help template)" + cli.SubcommandHelpTemplate = "(subcommand help template)" + + cases := []struct { + env map[string]string + cmd string + exp string + }{ + // help commands + { + cmd: "./gitea -h", + exp: "DEFAULT CONFIGURATION:", + }, + { + cmd: "./gitea help", + exp: "DEFAULT CONFIGURATION:", + }, + + { + cmd: "./gitea -c /dev/null -h", + exp: "ConfigFile: /dev/null", + }, + + { + cmd: "./gitea -c /dev/null help", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea help -c /dev/null", + exp: "ConfigFile: /dev/null", + }, + + { + cmd: "./gitea -c /dev/null test-cmd -h", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd -c /dev/null -h", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd -h -c /dev/null", + exp: "ConfigFile: /dev/null", + }, + + { + cmd: "./gitea -c /dev/null test-cmd help", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd -c /dev/null help", + exp: "ConfigFile: /dev/null", + }, + { + cmd: "./gitea test-cmd help -c /dev/null", + exp: "ConfigFile: /dev/null", + }, + + // parse paths + { + cmd: "./gitea test-cmd", + exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf), + }, + { + cmd: "./gitea -c /tmp/app.ini test-cmd", + exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), + }, + { + cmd: "./gitea test-cmd -c /tmp/app.ini", + exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), + }, + { + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmd: "./gitea test-cmd", + exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"), + }, + { + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmd: "./gitea test-cmd --work-path /tmp/other", + exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"), + }, + { + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmd: "./gitea test-cmd --config /tmp/app-other.ini", + exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"), + }, + } + + for _, c := range cases { + t.Run(c.cmd, func(t *testing.T) { + app := newTestApp(cli.Command{ + Action: func(ctx context.Context, cmd *cli.Command) error { + _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + return nil + }, + }) + for k, v := range c.env { + t.Setenv(k, v) + } + args := strings.Split(c.cmd, " ") // for test only, "split" is good enough + r, err := runTestApp(app, args...) + assert.NoError(t, err, c.cmd) + assert.NotEmpty(t, c.exp, c.cmd) + if !assert.Contains(t, r.Stdout, c.exp, c.cmd) { + t.Log("Full output:\n" + r.Stdout) + t.Log("Expected:\n" + c.exp) + } + }) + } +} + +func TestCliCmdError(t *testing.T) { + app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }}) + r, err := runTestApp(app, "./gitea", "test-cmd") + assert.Error(t, err) + assert.Equal(t, 1, r.ExitCode) + assert.Empty(t, r.Stdout) + assert.Equal(t, "Command error: normal error\n", r.Stderr) + + app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }}) + r, err = runTestApp(app, "./gitea", "test-cmd") + assert.Error(t, err) + assert.Equal(t, 2, r.ExitCode) + assert.Empty(t, r.Stdout) + assert.Equal(t, "exit error\n", r.Stderr) + + app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) + r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") + assert.Error(t, err) + assert.Equal(t, 1, r.ExitCode) + assert.Empty(t, r.Stdout) + assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) + + app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) + r, err = runTestApp(app, "./gitea", "test-cmd") + assert.NoError(t, err) + assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called + assert.Empty(t, r.Stdout) + assert.Empty(t, r.Stderr) +} + +func TestCliCmdBefore(t *testing.T) { + ctxNew := context.WithValue(context.Background(), any("key"), "value") + configValues := map[string]string{} + setting.CustomConf = "/tmp/any.ini" + var actionCtx context.Context + app := newTestApp(cli.Command{ + Before: func(context.Context, *cli.Command) (context.Context, error) { + configValues["before"] = setting.CustomConf + return ctxNew, nil + }, + Action: func(ctx context.Context, cmd *cli.Command) error { + configValues["action"] = setting.CustomConf + actionCtx = ctx + return nil + }, + }) + _, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd") + assert.NoError(t, err) + assert.Equal(t, ctxNew, actionCtx) + assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config") + assert.Equal(t, "/dev/null", configValues["action"]) +} diff --git a/cmd/cmd.go b/cmd/helper.go similarity index 100% rename from cmd/cmd.go rename to cmd/helper.go diff --git a/cmd/main.go b/cmd/main.go index a6b89a6fada..27d8cba2e90 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,7 +48,7 @@ DEFAULT CONFIGURATION: } } -func prepareSubcommandWithGlobalFlags(originCmd *cli.Command) { +func PrepareSubcommandWithGlobalFlags(originCmd *cli.Command) { originBefore := originCmd.Before originCmd.Before = func(ctxOrig context.Context, cmd *cli.Command) (ctx context.Context, err error) { ctx = ctxOrig @@ -145,7 +145,7 @@ func NewMainApp(appVer AppVersion) *cli.Command { app.Before = PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithConfig { - prepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) + PrepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) } app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdStandalone...) diff --git a/cmd/main_test.go b/cmd/main_test.go index 3cd2c984e64..f367bf12e42 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -5,17 +5,9 @@ package cmd import ( "context" - "errors" - "fmt" - "io" - "path/filepath" - "strings" "testing" "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/test" - "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" "github.com/urfave/cli/v3" @@ -25,209 +17,28 @@ func TestMain(m *testing.M) { unittest.MainTest(m) } -func makePathOutput(workPath, customPath, customConf string) string { - return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) -} - -func newTestApp(testCmd cli.Command) *cli.Command { - app := NewMainApp(AppVersion{}) - testCmd.Name = util.IfZero(testCmd.Name, "test-cmd") - prepareSubcommandWithGlobalFlags(&testCmd) - app.Commands = append(app.Commands, &testCmd) - app.DefaultCommand = testCmd.Name - return app -} - -type runResult struct { - Stdout string - Stderr string - ExitCode int -} - -func runTestApp(app *cli.Command, args ...string) (runResult, error) { - outBuf := new(strings.Builder) - errBuf := new(strings.Builder) - app.Writer = outBuf - app.ErrWriter = errBuf - exitCode := -1 - defer test.MockVariableValue(&cli.ErrWriter, app.ErrWriter)() - defer test.MockVariableValue(&cli.OsExiter, func(code int) { - if exitCode == -1 { - exitCode = code // save the exit code once and then reset the writer (to simulate the exit) - app.Writer, app.ErrWriter, cli.ErrWriter = io.Discard, io.Discard, io.Discard - } - })() - err := RunMainApp(app, args...) - return runResult{outBuf.String(), errBuf.String(), exitCode}, err -} - -func TestCliCmd(t *testing.T) { - defaultWorkPath := filepath.FromSlash("/tmp/mocked-work-path") - defaultCustomPath := filepath.Join(defaultWorkPath, "custom") - defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") - defer setting.MockBuiltinPaths(defaultWorkPath, "", "")() - - cli.CommandHelpTemplate = "(command help template)" - cli.RootCommandHelpTemplate = "(app help template)" - cli.SubcommandHelpTemplate = "(subcommand help template)" - - cases := []struct { - env map[string]string - cmd string - exp string - }{ - // help commands - { - cmd: "./gitea -h", - exp: "DEFAULT CONFIGURATION:", - }, - { - cmd: "./gitea help", - exp: "DEFAULT CONFIGURATION:", - }, - - { - cmd: "./gitea -c /dev/null -h", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea help -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null test-cmd -h", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -c /dev/null -h", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -h -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null test-cmd help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -c /dev/null help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd help -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - // parse paths - { - cmd: "./gitea test-cmd", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf), - }, - { - cmd: "./gitea -c /tmp/app.ini test-cmd", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), - }, - { - cmd: "./gitea test-cmd -c /tmp/app.ini", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), - }, - { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd", - exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"), - }, - { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd --work-path /tmp/other", - exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"), - }, - { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd --config /tmp/app-other.ini", - exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"), - }, - } - - for _, c := range cases { - t.Run(c.cmd, func(t *testing.T) { - app := newTestApp(cli.Command{ - Action: func(ctx context.Context, cmd *cli.Command) error { - _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) - return nil +func TestDefaultCommand(t *testing.T) { + test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) { + called := false + cmd := &cli.Command{ + DefaultCommand: "test", + Commands: []*cli.Command{ + { + Name: "test", + Action: func(ctx context.Context, command *cli.Command) error { + retName, retValid := isValidDefaultSubCommand(command) + assert.Equal(t, expectedRetName, retName) + assert.Equal(t, expectedRetValid, retValid) + called = true + return nil + }, }, - }) - for k, v := range c.env { - t.Setenv(k, v) - } - args := strings.Split(c.cmd, " ") // for test only, "split" is good enough - r, err := runTestApp(app, args...) - assert.NoError(t, err, c.cmd) - assert.NotEmpty(t, c.exp, c.cmd) - if !assert.Contains(t, r.Stdout, c.exp, c.cmd) { - t.Log("Full output:\n" + r.Stdout) - t.Log("Expected:\n" + c.exp) - } - }) + }, + } + assert.NoError(t, cmd.Run(t.Context(), args)) + assert.True(t, called) } -} - -func TestCliCmdError(t *testing.T) { - app := newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }}) - r, err := runTestApp(app, "./gitea", "test-cmd") - assert.Error(t, err) - assert.Equal(t, 1, r.ExitCode) - assert.Empty(t, r.Stdout) - assert.Equal(t, "Command error: normal error\n", r.Stderr) - - app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }}) - r, err = runTestApp(app, "./gitea", "test-cmd") - assert.Error(t, err) - assert.Equal(t, 2, r.ExitCode) - assert.Empty(t, r.Stdout) - assert.Equal(t, "exit error\n", r.Stderr) - - app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) - r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") - assert.Error(t, err) - assert.Equal(t, 1, r.ExitCode) - assert.Empty(t, r.Stdout) - assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) - - app = newTestApp(cli.Command{Action: func(ctx context.Context, cmd *cli.Command) error { return nil }}) - r, err = runTestApp(app, "./gitea", "test-cmd") - assert.NoError(t, err) - assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called - assert.Empty(t, r.Stdout) - assert.Empty(t, r.Stderr) -} - -func TestCliCmdBefore(t *testing.T) { - ctxNew := context.WithValue(context.Background(), any("key"), "value") - configValues := map[string]string{} - setting.CustomConf = "/tmp/any.ini" - var actionCtx context.Context - app := newTestApp(cli.Command{ - Before: func(context.Context, *cli.Command) (context.Context, error) { - configValues["before"] = setting.CustomConf - return ctxNew, nil - }, - Action: func(ctx context.Context, cmd *cli.Command) error { - configValues["action"] = setting.CustomConf - actionCtx = ctx - return nil - }, - }) - _, err := runTestApp(app, "./gitea", "--config", "/dev/null", "test-cmd") - assert.NoError(t, err) - assert.Equal(t, ctxNew, actionCtx) - assert.Equal(t, "/tmp/any.ini", configValues["before"], "BeforeFunc must be called before preparing config") - assert.Equal(t, "/dev/null", configValues["action"]) + test(t, []string{"./gitea"}, "", true) + test(t, []string{"./gitea", "test"}, "", true) + test(t, []string{"./gitea", "other"}, "other", false) } diff --git a/models/fixtures/hook_task.yml b/models/fixtures/hook_task.yml index e19eeb03687..01918b35eeb 100644 --- a/models/fixtures/hook_task.yml +++ b/models/fixtures/hook_task.yml @@ -1,39 +1,2 @@ -- - id: 1 - hook_id: 1 - uuid: uuid1 - is_delivered: true - is_succeed: false - request_content: > - { - "url": "/matrix-delivered", - "http_method":"PUT", - "headers": { - "X-Head": "42" - }, - "body": "{}" - } - -- - id: 2 - hook_id: 1 - uuid: uuid2 - is_delivered: true - -- - id: 3 - hook_id: 1 - uuid: uuid3 - is_delivered: true - is_succeed: true - payload_content: '{"key":"value"}' # legacy task, payload saved in payload_content (and not in request_content) - request_content: > - { - "url": "/matrix-success", - "http_method":"PUT", - "headers": { - "X-Head": "42" - } - } - +[] # DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/fixtures/webhook.yml b/models/fixtures/webhook.yml index f372aaaecb4..01918b35eeb 100644 --- a/models/fixtures/webhook.yml +++ b/models/fixtures/webhook.yml @@ -1,54 +1,2 @@ -- - id: 1 - repo_id: 1 - url: https://www.example.com/url1 - content_type: 1 # json - events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}' - is_active: true - -- - id: 2 - repo_id: 1 - url: https://www.example.com/url2 - content_type: 1 # json - events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' - is_active: false - -- - id: 3 - owner_id: 3 - repo_id: 3 - url: https://www.example.com/url3 - content_type: 1 # json - events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' - is_active: true - -- - id: 4 - repo_id: 2 - url: https://www.example.com/url4 - content_type: 1 # json - events: '{"push_only":true,"branch_filter":"{master,feature*}"}' - is_active: true - -- - id: 5 - repo_id: 0 - owner_id: 0 - url: https://www.example.com/url5 - content_type: 1 # json - events: '{"push_only":true,"branch_filter":"{master,feature*}"}' - is_active: true - is_system_webhook: true - -- - id: 6 - repo_id: 0 - owner_id: 0 - url: https://www.example.com/url6 - content_type: 1 # json - events: '{"push_only":true,"branch_filter":"{master,feature*}"}' - is_active: true - is_system_webhook: false - +[] # DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go index 117975928ca..7482829f1f2 100644 --- a/models/migrations/base/tests.go +++ b/models/migrations/base/tests.go @@ -75,7 +75,7 @@ func deleteDB() error { } db.Close() - // Check if we need to setup a specific schema + // Check if we need to set up a specific schema if len(setting.Database.Schema) != 0 { db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) @@ -91,7 +91,7 @@ func deleteDB() error { defer schrows.Close() if !schrows.Next() { - // Create and setup a DB schema + // Create and set up a DB schema _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema) if err != nil { return err @@ -134,7 +134,8 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu ourSkip := 2 ourSkip += skip deferFn := testlogger.PrintCurrentTest(t, ourSkip) - require.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + giteaRoot := setting.GetGiteaTestSourceRoot() + require.NoError(t, unittest.SyncDirs(filepath.Join(giteaRoot, "tests/gitea-repositories-meta"), setting.RepoRootPath)) if err := deleteDB(); err != nil { t.Fatalf("unable to reset database: %v", err) @@ -166,7 +167,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu } } - fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "migrations", "fixtures", t.Name()) + fixturesDir := filepath.Join(giteaRoot, "models", "migrations", "fixtures", t.Name()) if _, err := os.Stat(fixturesDir); err == nil { t.Logf("initializing fixtures from: %s", fixturesDir) @@ -202,17 +203,18 @@ func LoadTableSchemasMap(t *testing.T, x *xorm.Engine) map[string]*schemas.Table func mainTest(m *testing.M) int { testlogger.Init() - setting.SetupGiteaTestEnv() - tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data") + tempWorkPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("migration-test-data-") if err != nil { - testlogger.Panicf("Unable to create temporary data path %v\n", err) + return testlogger.MainErrorf("Unable to create temporary dir for migration test: %v", err) } defer cleanup() - setting.AppDataPath = tmpDataPath + + setting.MockBuiltinPaths(tempWorkPath, "", "") + setting.SetupGiteaTestEnv() if err = git.InitFull(); err != nil { - testlogger.Panicf("Unable to InitFull: %v\n", err) + return testlogger.MainErrorf("Unable to InitFull: %v", err) } setting.LoadDBSetting() setting.InitLoggersForTest() diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 0c1458e2ce5..bd832348e7c 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -42,12 +42,20 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { func mainTest(m *testing.M, testOptsArg ...*TestOptions) int { testOpts := util.OptionalArg(testOptsArg, &TestOptions{}) + + tempWorkPath, tempCleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("unit-test-dir-") + if err != nil { + return testlogger.MainErrorf("Failed to create temp dir for unit test: %v", err) + } + defer tempCleanup() + + defer setting.MockBuiltinPaths(tempWorkPath, "", "")() setting.SetupGiteaTestEnv() + giteaRoot := setting.GetGiteaTestSourceRoot() fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles} if err := CreateTestEngine(fixturesOpts); err != nil { - _, _ = fmt.Fprintf(os.Stderr, "Error creating test database engine: %v\n", err) - os.Exit(1) + return testlogger.MainErrorf("Error creating test database engine: %v", err) } setting.AppURL = "https://try.gitea.io/" @@ -59,59 +67,28 @@ func mainTest(m *testing.M, testOptsArg ...*TestOptions) int { setting.SSH.Domain = "try.gitea.io" setting.Database.Type = "sqlite3" setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" - repoRootPath, cleanup1, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("repos") - if err != nil { - testlogger.Panicf("TempDir: %v\n", err) - } - defer cleanup1() - - setting.RepoRootPath = repoRootPath - appDataPath, cleanup2, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("appdata") - if err != nil { - testlogger.Panicf("TempDir: %v\n", err) - } - defer cleanup2() - - setting.AppDataPath = appDataPath setting.GravatarSource = "https://secure.gravatar.com/avatar/" - - setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments") - - setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs") - - setting.Avatar.Storage.Path = filepath.Join(setting.AppDataPath, "avatars") - - setting.RepoAvatar.Storage.Path = filepath.Join(setting.AppDataPath, "repo-avatars") - - setting.RepoArchive.Storage.Path = filepath.Join(setting.AppDataPath, "repo-archive") - - setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") - - setting.Actions.LogStorage.Path = filepath.Join(setting.AppDataPath, "actions_log") - - setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home") - setting.IncomingEmail.ReplyToAddress = "incoming+%{token}@localhost" config.SetDynGetter(system.NewDatabaseDynKeyGetter()) if err = cache.Init(); err != nil { - testlogger.Panicf("cache.Init: %v\n", err) + return testlogger.MainErrorf("cache.Init: %v", err) } if err = storage.Init(); err != nil { - testlogger.Panicf("storage.Init: %v\n", err) + return testlogger.MainErrorf("storage.Init: %v", err) } if err = SyncDirs(filepath.Join(giteaRoot, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { - testlogger.Panicf("util.SyncDirs: %v\n", err) + return testlogger.MainErrorf("util.SyncDirs: %v", err) } if err = git.InitFull(); err != nil { - testlogger.Panicf("git.Init: %v\n", err) + return testlogger.MainErrorf("git.Init: %v", err) } if testOpts.SetUp != nil { if err := testOpts.SetUp(); err != nil { - testlogger.Panicf("set up failed: %v\n", err) + return testlogger.MainErrorf("set up failed: %v", err) } } @@ -119,7 +96,7 @@ func mainTest(m *testing.M, testOptsArg ...*TestOptions) int { if testOpts.TearDown != nil { if err := testOpts.TearDown(); err != nil { - testlogger.Panicf("tear down failed: %v\n", err) + return testlogger.MainErrorf("tear down failed: %v", err) } } return exitStatus diff --git a/models/webhook/main_test.go b/models/webhook/main_test.go index f19465d5053..5f2d5081a1a 100644 --- a/models/webhook/main_test.go +++ b/models/webhook/main_test.go @@ -15,5 +15,6 @@ func TestMain(m *testing.M) { "webhook.yml", "hook_task.yml", }, + SetUp: prepareWebhookTestData, }) } diff --git a/models/webhook/webhook_system_test.go b/models/webhook/webhook_system_test.go index d0013c6873f..9e954d1e377 100644 --- a/models/webhook/webhook_system_test.go +++ b/models/webhook/webhook_system_test.go @@ -10,28 +10,28 @@ import ( "code.gitea.io/gitea/modules/optional" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestListSystemWebhookOptions(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hookSystem := unittest.AssertExistsAndLoadBean(t, &Webhook{URL: "https://www.example.com/system"}) + hookDefault := unittest.AssertExistsAndLoadBean(t, &Webhook{URL: "https://www.example.com/default"}) opts := ListSystemWebhookOptions{IsSystem: optional.None[bool]()} hooks, _, err := GetGlobalWebhooks(t.Context(), &opts) - assert.NoError(t, err) - if assert.Len(t, hooks, 2) { - assert.Equal(t, int64(5), hooks[0].ID) - assert.Equal(t, int64(6), hooks[1].ID) - } + require.NoError(t, err) + require.Len(t, hooks, 2) + assert.Equal(t, hookSystem.ID, hooks[0].ID) + assert.Equal(t, hookDefault.ID, hooks[1].ID) + opts.IsSystem = optional.Some(true) hooks, _, err = GetGlobalWebhooks(t.Context(), &opts) - assert.NoError(t, err) - if assert.Len(t, hooks, 1) { - assert.Equal(t, int64(5), hooks[0].ID) - } + require.NoError(t, err) + require.Len(t, hooks, 1) + assert.Equal(t, hookSystem.ID, hooks[0].ID) opts.IsSystem = optional.Some(false) hooks, _, err = GetGlobalWebhooks(t.Context(), &opts) - assert.NoError(t, err) - if assert.Len(t, hooks, 1) { - assert.Equal(t, int64(6), hooks[0].ID) - } + require.NoError(t, err) + require.Len(t, hooks, 1) + assert.Equal(t, hookDefault.ID, hooks[0].ID) } diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index 71f50017c51..073af91de21 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -4,6 +4,7 @@ package webhook import ( + "context" "testing" "time" @@ -14,40 +15,105 @@ import ( "code.gitea.io/gitea/modules/timeutil" webhook_module "code.gitea.io/gitea/modules/webhook" + "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "xorm.io/builder" ) -func TestHookContentType_Name(t *testing.T) { - assert.Equal(t, "json", ContentTypeJSON.Name()) - assert.Equal(t, "form", ContentTypeForm.Name()) +func prepareWebhookTestData() error { + if err := unittest.PrepareTestDatabase(); err != nil { + return err + } + var hooks []*Webhook + hooks = append(hooks, &Webhook{ + RepoID: 1, + URL: "https://www.example.com/url1", + ContentType: ContentTypeJSON, + Events: `{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}`, + IsActive: true, + }) + hooks = append(hooks, &Webhook{ + RepoID: 1, + URL: "https://www.example.com/url2", + ContentType: ContentTypeJSON, + Events: `{}`, + IsActive: false, + }) + hooks = append(hooks, &Webhook{ + OwnerID: 3, + RepoID: 3, + URL: "https://www.example.com/url3", + ContentType: ContentTypeJSON, + Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`, + IsActive: true, + }) + hooks = append(hooks, &Webhook{ + OwnerID: 3, + RepoID: 3, + URL: "https://www.example.com/url3", + ContentType: ContentTypeJSON, + Events: `{}`, + }) + hooks = append(hooks, &Webhook{ + RepoID: 2, + URL: "https://www.example.com/url4", + ContentType: ContentTypeJSON, + Events: `{"push_only":true,"branch_filter":"{master,feature*}"}`, + IsActive: true, + }) + hooks = append(hooks, &Webhook{ + URL: "https://www.example.com/system", + ContentType: ContentTypeJSON, + Events: `{"push_only":true,"branch_filter":"{master,feature*}"}`, + IsSystemWebhook: true, + }) + hooks = append(hooks, &Webhook{ + URL: "https://www.example.com/default", + ContentType: ContentTypeJSON, + Events: `{"push_only":true,"branch_filter":"{master,feature*}"}`, + }) + ctx := context.Background() + if err := db.TruncateBeans(ctx, &Webhook{}); err != nil { + return err + } + if err := db.Insert(ctx, hooks); err != nil { + return err + } + + hook, _, _ := db.Get[Webhook](ctx, builder.Eq{"repo_id": 1, "is_active": true}) + var tasks []*HookTask + tasks = append(tasks, &HookTask{HookID: hook.ID, UUID: uuid.New().String()}) + tasks = append(tasks, &HookTask{HookID: hook.ID, UUID: uuid.New().String()}) + tasks = append(tasks, &HookTask{HookID: hook.ID, UUID: uuid.New().String()}) + if err := db.TruncateBeans(ctx, &HookTask{}); err != nil { + return err + } + return db.Insert(ctx, tasks) } -func TestIsValidHookContentType(t *testing.T) { +func TestWebHookContentType(t *testing.T) { + assert.Equal(t, "json", ContentTypeJSON.Name()) + assert.Equal(t, "form", ContentTypeForm.Name()) assert.True(t, IsValidHookContentType("json")) assert.True(t, IsValidHookContentType("form")) assert.False(t, IsValidHookContentType("invalid")) } func TestWebhook_History(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}) - tasks, err := webhook.History(t.Context(), 0) - assert.NoError(t, err) - if assert.Len(t, tasks, 3) { - assert.Equal(t, int64(3), tasks[0].ID) - assert.Equal(t, int64(2), tasks[1].ID) - assert.Equal(t, int64(1), tasks[2].ID) - } + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, IsActive: true}) + tasks, err := hook.History(t.Context(), 0) + require.NoError(t, err) + require.Len(t, tasks, 3) - webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) - tasks, err = webhook.History(t.Context(), 0) + hook = unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, Events: "{}"}) + tasks, err = hook.History(t.Context(), 0) assert.NoError(t, err) assert.Empty(t, tasks) } func TestWebhook_UpdateEvent(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1}) + webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, IsActive: true}) hookEvent := &webhook_module.HookEvent{ PushOnly: true, SendEverything: false, @@ -100,10 +166,10 @@ func TestCreateWebhook(t *testing.T) { } func TestGetWebhookByRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - hook, err := GetWebhookByRepoID(t.Context(), 1, 1) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, IsActive: true}) + loaded, err := GetWebhookByRepoID(t.Context(), 1, hook.ID) assert.NoError(t, err) - assert.Equal(t, int64(1), hook.ID) + assert.Equal(t, hook.ID, loaded.ID) _, err = GetWebhookByRepoID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) @@ -111,10 +177,10 @@ func TestGetWebhookByRepoID(t *testing.T) { } func TestGetWebhookByOwnerID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - hook, err := GetWebhookByOwnerID(t.Context(), 3, 3) - assert.NoError(t, err) - assert.Equal(t, int64(3), hook.ID) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{OwnerID: 3}) + loaded, err := GetWebhookByOwnerID(t.Context(), 3, hook.ID) + require.NoError(t, err) + require.Equal(t, hook.ID, loaded.ID) _, err = GetWebhookByOwnerID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) assert.Error(t, err) @@ -122,48 +188,45 @@ func TestGetWebhookByOwnerID(t *testing.T) { } func TestGetActiveWebhooksByRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, IsActive: true}) hooks, err := db.Find[Webhook](t.Context(), ListWebhookOptions{RepoID: 1, IsActive: optional.Some(true)}) - assert.NoError(t, err) - if assert.Len(t, hooks, 1) { - assert.Equal(t, int64(1), hooks[0].ID) - assert.True(t, hooks[0].IsActive) - } + require.NoError(t, err) + require.Len(t, hooks, 1) + assert.Equal(t, hook.ID, hooks[0].ID) + assert.True(t, hooks[0].IsActive) } func TestGetWebhooksByRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) hooks, err := db.Find[Webhook](t.Context(), ListWebhookOptions{RepoID: 1}) - assert.NoError(t, err) - if assert.Len(t, hooks, 2) { - assert.Equal(t, int64(1), hooks[0].ID) - assert.Equal(t, int64(2), hooks[1].ID) - } + require.NoError(t, err) + require.Len(t, hooks, 2) + assert.Equal(t, int64(1), hooks[0].RepoID) + assert.True(t, hooks[0].IsActive) + assert.Equal(t, int64(1), hooks[1].RepoID) + assert.False(t, hooks[1].IsActive) } func TestGetActiveWebhooksByOwnerID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) hooks, err := db.Find[Webhook](t.Context(), ListWebhookOptions{OwnerID: 3, IsActive: optional.Some(true)}) - assert.NoError(t, err) - if assert.Len(t, hooks, 1) { - assert.Equal(t, int64(3), hooks[0].ID) - assert.True(t, hooks[0].IsActive) - } + require.NoError(t, err) + require.Len(t, hooks, 1) + assert.Equal(t, int64(3), hooks[0].OwnerID) + assert.True(t, hooks[0].IsActive) } func TestGetWebhooksByOwnerID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) hooks, err := db.Find[Webhook](t.Context(), ListWebhookOptions{OwnerID: 3}) - assert.NoError(t, err) - if assert.Len(t, hooks, 1) { - assert.Equal(t, int64(3), hooks[0].ID) - assert.True(t, hooks[0].IsActive) - } + require.NoError(t, err) + require.Len(t, hooks, 2) + assert.Equal(t, int64(3), hooks[0].OwnerID) + assert.True(t, hooks[0].IsActive) + assert.Equal(t, int64(3), hooks[1].OwnerID) + assert.False(t, hooks[1].IsActive) } func TestUpdateWebhook(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - hook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2}) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, Events: `{}`}) + require.False(t, hook.IsActive) hook.IsActive = true hook.ContentType = ContentTypeForm unittest.AssertNotExistsBean(t, hook) @@ -172,48 +235,36 @@ func TestUpdateWebhook(t *testing.T) { } func TestDeleteWebhookByRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1}) - assert.NoError(t, DeleteWebhookByRepoID(t.Context(), 1, 2)) - unittest.AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1}) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, Events: `{}`}) + assert.NoError(t, DeleteWebhookByRepoID(t.Context(), 1, hook.ID)) + unittest.AssertNotExistsBean(t, &Webhook{ID: hook.ID, RepoID: 1}) err := DeleteWebhookByRepoID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) - assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestDeleteWebhookByOwnerID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3}) - assert.NoError(t, DeleteWebhookByOwnerID(t.Context(), 3, 3)) - unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3}) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{OwnerID: 3, Events: `{}`}) + assert.NoError(t, DeleteWebhookByOwnerID(t.Context(), 3, hook.ID)) + unittest.AssertNotExistsBean(t, &Webhook{ID: hook.ID, OwnerID: 3}) err := DeleteWebhookByOwnerID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) - assert.Error(t, err) assert.True(t, IsErrWebhookNotExist(err)) } func TestHookTasks(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - hookTasks, err := HookTasks(t.Context(), 1, 1) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{RepoID: 1, IsActive: true}) + hookTasks, err := HookTasks(t.Context(), hook.ID, 1) assert.NoError(t, err) - if assert.Len(t, hookTasks, 3) { - assert.Equal(t, int64(3), hookTasks[0].ID) - assert.Equal(t, int64(2), hookTasks[1].ID) - assert.Equal(t, int64(1), hookTasks[2].ID) - } - + assert.Len(t, hookTasks, 3) hookTasks, err = HookTasks(t.Context(), unittest.NonexistentID, 1) assert.NoError(t, err) assert.Empty(t, hookTasks) } func TestCreateHookTask(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - hookTask := &HookTask{ - HookID: 3, - PayloadVersion: 2, - } + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{OwnerID: 3, IsActive: true}) + hookTask := &HookTask{HookID: hook.ID, PayloadVersion: 2} unittest.AssertNotExistsBean(t, hookTask) _, err := CreateHookTask(t.Context(), hookTask) assert.NoError(t, err) @@ -221,20 +272,23 @@ func TestCreateHookTask(t *testing.T) { } func TestUpdateHookTask(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := unittest.AssertExistsAndLoadBean(t, &Webhook{OwnerID: 3, IsActive: true}) + hookTask := &HookTask{HookID: hook.ID, PayloadVersion: 2} + _, err := CreateHookTask(t.Context(), hookTask) + assert.NoError(t, err) - hook := unittest.AssertExistsAndLoadBean(t, &HookTask{ID: 1}) - hook.PayloadContent = "new payload content" - hook.IsDelivered = true - unittest.AssertNotExistsBean(t, hook) - assert.NoError(t, UpdateHookTask(t.Context(), hook)) - unittest.AssertExistsAndLoadBean(t, hook) + hookTask.PayloadContent = "new payload content" + hookTask.IsDelivered = true + unittest.AssertNotExistsBean(t, hookTask) + assert.NoError(t, UpdateHookTask(t.Context(), hookTask)) + unittest.AssertExistsAndLoadBean(t, hookTask) } func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup1", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) hookTask := &HookTask{ - HookID: 3, + HookID: hook.ID, IsDelivered: true, Delivered: timeutil.TimeStampNanoNow(), PayloadVersion: 2, @@ -249,9 +303,10 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { } func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup2", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) hookTask := &HookTask{ - HookID: 4, + HookID: hook.ID, IsDelivered: false, PayloadVersion: 2, } @@ -265,9 +320,10 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) { } func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup3", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) hookTask := &HookTask{ - HookID: 4, + HookID: hook.ID, IsDelivered: true, Delivered: timeutil.TimeStampNanoNow(), PayloadVersion: 2, @@ -282,9 +338,10 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { } func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup4", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) hookTask := &HookTask{ - HookID: 3, + HookID: hook.ID, IsDelivered: true, Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()), PayloadVersion: 2, @@ -299,9 +356,10 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { } func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup5", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) hookTask := &HookTask{ - HookID: 4, + HookID: hook.ID, IsDelivered: false, PayloadVersion: 2, } @@ -315,9 +373,10 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) { } func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup6", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) hookTask := &HookTask{ - HookID: 4, + HookID: hook.ID, IsDelivered: true, Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()), PayloadVersion: 2, diff --git a/modules/git/git.go b/modules/git/git.go index 2df83f9843a..69eb07d1f0b 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -192,13 +192,13 @@ func RunGitTests(m interface{ Run() int }) { func runGitTests(m interface{ Run() int }) int { gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home") if err != nil { - testlogger.Panicf("unable to create temp dir: %s", err.Error()) + return testlogger.MainErrorf("unable to create temp dir: %v", err) } defer cleanup() setting.Git.HomePath = gitHomePath if err = InitFull(); err != nil { - testlogger.Panicf("failed to call Init: %s", err.Error()) + return testlogger.MainErrorf("failed to call Init: %v", err) } return m.Run() } diff --git a/modules/git/gitcmd/command_test.go b/modules/git/gitcmd/command_test.go index 6e4214d9953..19ec02b8088 100644 --- a/modules/git/gitcmd/command_test.go +++ b/modules/git/gitcmd/command_test.go @@ -24,7 +24,7 @@ func testMain(m *testing.M) int { // "setting.Git.HomePath" is initialized in "git" package but really used in "gitcmd" package gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home") if err != nil { - testlogger.Panicf("failed to create temp dir: %v", err) + return testlogger.MainErrorf("failed to create temp dir: %v", err) } defer cleanup() diff --git a/modules/queue/manager_test.go b/modules/queue/manager_test.go index fda498cc841..f9f9b7310be 100644 --- a/modules/queue/manager_test.go +++ b/modules/queue/manager_test.go @@ -13,11 +13,7 @@ import ( ) func TestManager(t *testing.T) { - oldAppDataPath := setting.AppDataPath setting.AppDataPath = t.TempDir() - defer func() { - setting.AppDataPath = oldAppDataPath - }() newQueueFromConfig := func(name, cfg string) (*WorkerPoolQueue[int], error) { cfgProvider, err := setting.NewConfigProviderFromData(cfg) diff --git a/modules/setting/testenv.go b/modules/setting/testenv.go index 27bf72e860a..d8663d07e24 100644 --- a/modules/setting/testenv.go +++ b/modules/setting/testenv.go @@ -15,7 +15,7 @@ import ( "code.gitea.io/gitea/modules/util" ) -var giteaTestSourceRoot *string +var giteaTestSourceRoot *string // intentionally use a pointer to make sure the uninitialized access panics func GetGiteaTestSourceRoot() string { return *giteaTestSourceRoot @@ -30,7 +30,9 @@ func SetupGiteaTestEnv() { log.OsExiter = func(code int) { if code != 0 { - // non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens, show a full stacktrace for more details + // Non-zero exit code (log.Fatal) shouldn't occur during testing, if it happens: + // * Show a full stacktrace for more details. + // * If the "log.Fatal" is abused in tests, should fix. panic(fmt.Errorf("non-zero exit code during testing: %d", code)) } os.Exit(0) @@ -49,12 +51,14 @@ func SetupGiteaTestEnv() { giteaTestSourceRoot = &giteaRoot return giteaRoot } + giteaRoot := initGiteaRoot() initGiteaPaths := func() { - appWorkPathBuiltin = *giteaTestSourceRoot - AppWorkPath = appWorkPathBuiltin - AppPath = filepath.Join(AppWorkPath, "gitea") + util.Iif(IsWindows, ".exe", "") - StaticRootPath = AppWorkPath // need to load assets (options, public) from the source code directory for testing + // need to load assets (options, public) from the source code directory for testing + StaticRootPath = giteaRoot + // during testing, the AppPath must point to the pre-built Gitea binary in the source root + // it needs to be called by git hooks + AppPath = filepath.Join(giteaRoot, "gitea") + util.Iif(IsWindows, ".exe", "") } initGiteaConf := func() string { @@ -62,13 +66,15 @@ func SetupGiteaTestEnv() { giteaConf := os.Getenv("GITEA_TEST_CONF") if giteaConf == "" { // if no GITEA_TEST_CONF, then it is in unit test, use a temp (non-existing / empty) config file + // do not really use such config file, the test can run concurrently, using the same config file will cause data-race between tests giteaConf = "custom/conf/app-test-tmp.ini" customConfBuiltin = filepath.Join(AppWorkPath, giteaConf) CustomConf = customConfBuiltin _ = os.Remove(CustomConf) } else { - // CustomConf must be absolute path to make tests pass, - CustomConf = filepath.Join(AppWorkPath, giteaConf) + // CustomConf must be absolute path to make tests pass. + // At the moment, GITEA_TEST_CONF is always in Gitea's source root + CustomConf = filepath.Join(giteaRoot, giteaConf) } return giteaConf } @@ -98,12 +104,15 @@ func SetupGiteaTestEnv() { PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") } - giteaRoot := initGiteaRoot() initGiteaPaths() giteaConf := initGiteaConf() cleanUpEnv() initWorkPathAndConfig() + if RepoRootPath == "" || AppDataPath == "" { + panic("SetupGiteaTestEnv failed, paths are not initialized") + } + // TODO: some git repo hooks (test fixtures) still use these env variables, need to be refactored in the future _ = os.Setenv("GITEA_ROOT", giteaRoot) _ = os.Setenv("GITEA_CONF", giteaConf) // test fixture git hooks use "$GITEA_ROOT/$GITEA_CONF" in their scripts diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go index 217121f604b..151ca397032 100644 --- a/modules/testlogger/testlogger.go +++ b/modules/testlogger/testlogger.go @@ -118,7 +118,7 @@ func PrintCurrentTest(t testing.TB, skip ...int) func() { deferHasRun := false t.Cleanup(func() { if !deferHasRun { - stdoutPrintf("!!! %s defer function hasn't been run but Cleanup is called, usually caused by panic\n", t.Name()) + stdoutPrintf("!!! %s: defer function hasn't been run but Cleanup is called, usually caused by panic\n", t.Name()) } }) stdoutPrintf("=== %s (%s:%d)\n", log.NewColoredValue(t.Name()), strings.TrimPrefix(filename, prefix), line) @@ -173,7 +173,8 @@ func Init() { log.RegisterEventWriter("test", newTestLoggerWriter) } -func Panicf(format string, args ...any) { - // don't call os.Exit, otherwise the "defer" functions won't be executed - panic(fmt.Sprintf(format, args...)) +// MainErrorf is used to report an error from TestMain and return a non-zero value to indicate the failure +func MainErrorf(msg string, a ...any) int { + _, _ = fmt.Fprintf(os.Stderr, msg+"\n", a...) + return 1 } diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go index f8d61ccf000..6b2c7627d09 100644 --- a/routers/api/v1/repo/hook_test.go +++ b/routers/api/v1/repo/hook_test.go @@ -5,8 +5,10 @@ package repo import ( "net/http" + "strconv" "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/services/contexttest" @@ -17,8 +19,17 @@ import ( func TestTestHook(t *testing.T) { unittest.PrepareTestEnv(t) + hook := &webhook.Webhook{ + RepoID: 1, + URL: "https://www.example.com/test_hook", + ContentType: webhook.ContentTypeJSON, + Events: `{"push_only":true}`, + IsActive: true, + } + assert.NoError(t, db.Insert(t.Context(), hook)) + ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages") - ctx.SetPathParam("id", "1") + ctx.SetPathParam("id", strconv.FormatInt(hook.ID, 10)) contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepoCommit(t, ctx) contexttest.LoadUser(t, ctx, 2) @@ -26,6 +37,6 @@ func TestTestHook(t *testing.T) { assert.Equal(t, http.StatusNoContent, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{ - HookID: 1, + HookID: hook.ID, }, unittest.Cond("is_delivered=?", false)) } diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index 58fba9f68df..105d834b826 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -36,10 +36,7 @@ import ( func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (req *http.Request, body []byte, err error) { switch w.HTTPMethod { - case "": - log.Info("HTTP Method for %s webhook %s [ID: %d] is not set, defaulting to POST", w.Type, w.URL, w.ID) - fallthrough - case http.MethodPost: + case "", http.MethodPost: switch w.ContentType { case webhook_model.ContentTypeJSON: req, err = http.NewRequest(http.MethodPost, w.URL, strings.NewReader(t.PayloadContent)) diff --git a/services/webhook/webhook_test.go b/services/webhook/webhook_test.go index f4432cc3f11..8522cce8eca 100644 --- a/services/webhook/webhook_test.go +++ b/services/webhook/webhook_test.go @@ -6,6 +6,7 @@ package webhook import ( "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -21,7 +22,17 @@ import ( "github.com/stretchr/testify/require" ) -func TestWebhook_GetSlackHook(t *testing.T) { +func TestWebhookService(t *testing.T) { + unittest.PrepareTestEnv(t) + t.Run("GetSlackHook", testWebhookGetSlackHook) + t.Run("PrepareWebhooks", testWebhookPrepare) + t.Run("PrepareBranchFilterMatch", testWebhookPrepareBranchFilterMatch) + t.Run("PrepareBranchFilterNoMatch", testWebhookPrepareBranchFilterNoMatch) + t.Run("WebhookUserMail", testWebhookUserMail) + t.Run("CheckBranchFilter", testWebhookCheckBranchFilter) +} + +func testWebhookGetSlackHook(t *testing.T) { w := &webhook_model.Webhook{ Meta: `{"channel": "foo", "username": "username", "color": "blue"}`, } @@ -33,66 +44,69 @@ func TestWebhook_GetSlackHook(t *testing.T) { }, *slackHook) } -func TestPrepareWebhooks(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - +func testWebhookPrepare(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - hookTasks := []*webhook_model.HookTask{ - {HookID: 1, EventType: webhook_module.HookEventPush}, - } - for _, hookTask := range hookTasks { - unittest.AssertNotExistsBean(t, hookTask) - } - assert.NoError(t, PrepareWebhooks(t.Context(), EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Commits: []*api.PayloadCommit{{}}})) - for _, hookTask := range hookTasks { - unittest.AssertExistsAndLoadBean(t, hookTask) + hook := &webhook_model.Webhook{ + RepoID: repo.ID, + URL: "http://localhost/gitea-webhook-test-prepare_webhooks", + ContentType: webhook_model.ContentTypeJSON, + Events: `{"push_only":true}`, + IsActive: true, } + require.NoError(t, db.Insert(t.Context(), hook)) + + hookTask := &webhook_model.HookTask{HookID: hook.ID, EventType: webhook_module.HookEventPush} + unittest.AssertNotExistsBean(t, hookTask) + err := PrepareWebhooks(t.Context(), EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Commits: []*api.PayloadCommit{{}}}) + require.NoError(t, err) + unittest.AssertExistsAndLoadBean(t, hookTask) } -func TestPrepareWebhooksBranchFilterMatch(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - +func testWebhookPrepareBranchFilterMatch(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - hookTasks := []*webhook_model.HookTask{ - {HookID: 4, EventType: webhook_module.HookEventPush}, - } - for _, hookTask := range hookTasks { - unittest.AssertNotExistsBean(t, hookTask) + hook := &webhook_model.Webhook{ + RepoID: repo.ID, + URL: "http://localhost/gitea-webhook-test-branch_filter_match", + ContentType: webhook_model.ContentTypeJSON, + Events: `{"push_only":true,"branch_filter":"{master,feature*}"}`, + IsActive: true, } + require.NoError(t, db.Insert(t.Context(), hook)) + + hookTask := &webhook_model.HookTask{HookID: hook.ID, EventType: webhook_module.HookEventPush} + unittest.AssertNotExistsBean(t, hookTask) // this test also ensures that * doesn't handle / in any special way (like shell would) - assert.NoError(t, PrepareWebhooks(t.Context(), EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791", Commits: []*api.PayloadCommit{{}}})) - for _, hookTask := range hookTasks { - unittest.AssertExistsAndLoadBean(t, hookTask) - } + err := PrepareWebhooks(t.Context(), EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Ref: "refs/heads/feature/7791", Commits: []*api.PayloadCommit{{}}}) + require.NoError(t, err) + unittest.AssertExistsAndLoadBean(t, hookTask) } -func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - +func testWebhookPrepareBranchFilterNoMatch(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - hookTasks := []*webhook_model.HookTask{ - {HookID: 4, EventType: webhook_module.HookEventPush}, + hook := &webhook_model.Webhook{ + RepoID: repo.ID, + URL: "http://localhost/gitea-webhook-test-branch_filter_no_match", + ContentType: webhook_model.ContentTypeJSON, + Events: `{"push_only":true,"branch_filter":"{master,feature*}"}`, + IsActive: true, } - for _, hookTask := range hookTasks { - unittest.AssertNotExistsBean(t, hookTask) - } - assert.NoError(t, PrepareWebhooks(t.Context(), EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Ref: "refs/heads/fix_weird_bug"})) + require.NoError(t, db.Insert(t.Context(), hook)) - for _, hookTask := range hookTasks { - unittest.AssertNotExistsBean(t, hookTask) - } + hookTask := &webhook_model.HookTask{HookID: hook.ID, EventType: webhook_module.HookEventPush} + unittest.AssertNotExistsBean(t, hookTask) + err := PrepareWebhooks(t.Context(), EventSource{Repository: repo}, webhook_module.HookEventPush, &api.PushPayload{Ref: "refs/heads/fix_weird_bug"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, hookTask) } -func TestWebhookUserMail(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) +func testWebhookUserMail(t *testing.T) { defer test.MockVariableValue(&setting.Service.NoReplyAddress, "no-reply.com")() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) assert.Equal(t, user.GetPlaceholderEmail(), convert.ToUser(t.Context(), user, nil).Email) assert.Equal(t, user.Email, convert.ToUser(t.Context(), user, user).Email) } -func TestCheckBranchFilter(t *testing.T) { +func testWebhookCheckBranchFilter(t *testing.T) { cases := []struct { filter string ref git.RefName diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 33be7d89cb6..60dc7e77a48 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -87,16 +87,19 @@ func testMain(m *testing.M) int { graceful.InitManager(managerCtx) defer cancel() - tests.InitTest() + err := tests.InitTest() + if err != nil { + return testlogger.MainErrorf("InitTest error: %v", err) + } testWebRoutes = routers.NormalRoutes() - err := unittest.InitFixtures( + err = unittest.InitFixtures( unittest.FixturesOptions{ - Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), + Dir: filepath.Join(setting.GetGiteaTestSourceRoot(), "models/fixtures/"), }, ) if err != nil { - testlogger.Panicf("InitFixtures: %v", err) + return testlogger.MainErrorf("InitFixtures: %v", err) } // FIXME: the console logger is deleted by mistake, so if there is any `log.Fatal`, developers won't see any error message. diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go index 1e1dc7bf142..c1acdb999b1 100644 --- a/tests/integration/migration-test/migration_test.go +++ b/tests/integration/migration-test/migration_test.go @@ -40,7 +40,7 @@ func initMigrationTest(t *testing.T) func() { setting.SetupGiteaTestEnv() assert.NotEmpty(t, setting.RepoRootPath) - assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/gitea-repositories-meta"), setting.RepoRootPath)) assert.NoError(t, git.InitFull()) setting.LoadDBSetting() setting.InitLoggersForTest() diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index bf237a4fc6f..3d24c4c3264 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -17,6 +17,7 @@ import ( "time" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" pull_model "code.gitea.io/gitea/models/pull" @@ -92,12 +93,25 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str return resp } +func preparePullMergeWebhook(t *testing.T, repoID int64) { + require.NoError(t, db.TruncateBeans(t.Context(), &webhook.Webhook{}, &webhook.HookTask{})) + require.NoError(t, db.Insert(t.Context(), &webhook.Webhook{ + RepoID: repoID, + URL: "http://localhost/gitea-test-webhook-pull-merge", + ContentType: webhook.ContentTypeJSON, + Events: `{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}`, + IsActive: true, + })) +} + +func assertPullMergeWebhookTask(t *testing.T, repoID int64) { + hook := unittest.AssertExistsAndLoadBean(t, &webhook.Webhook{RepoID: repoID}) + unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{HookID: hook.ID}) +} + func TestPullMerge(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - hookTasks, err := webhook.HookTasks(t.Context(), 1, 1) // Retrieve previous hook number - assert.NoError(t, err) - hookTasksLenBefore := len(hookTasks) - + preparePullMergeWebhook(t, 1) session := loginUser(t, "user1") // FIXME: don't use admin user for testing testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -123,18 +137,13 @@ func TestPullMerge(t *testing.T) { assert.Equal(t, 4, repo.NumPulls) assert.Equal(t, 3, repo.NumOpenPulls) - hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) - assert.NoError(t, err) - assert.Len(t, hookTasks, hookTasksLenBefore+1) + assertPullMergeWebhookTask(t, 1) }) } func TestPullRebase(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - hookTasks, err := webhook.HookTasks(t.Context(), 1, 1) // Retrieve previous hook number - assert.NoError(t, err) - hookTasksLenBefore := len(hookTasks) - + preparePullMergeWebhook(t, 1) session := loginUser(t, "user1") // FIXME: don't use admin user for testing testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -160,18 +169,13 @@ func TestPullRebase(t *testing.T) { assert.Equal(t, 4, repo.NumPulls) assert.Equal(t, 3, repo.NumOpenPulls) - hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) - assert.NoError(t, err) - assert.Len(t, hookTasks, hookTasksLenBefore+1) + assertPullMergeWebhookTask(t, 1) }) } func TestPullRebaseMerge(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - hookTasks, err := webhook.HookTasks(t.Context(), 1, 1) // Retrieve previous hook number - assert.NoError(t, err) - hookTasksLenBefore := len(hookTasks) - + preparePullMergeWebhook(t, 1) session := loginUser(t, "user1") // FIXME: don't use admin user for testing testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -197,18 +201,13 @@ func TestPullRebaseMerge(t *testing.T) { assert.Equal(t, 4, repo.NumPulls) assert.Equal(t, 3, repo.NumOpenPulls) - hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) - assert.NoError(t, err) - assert.Len(t, hookTasks, hookTasksLenBefore+1) + assertPullMergeWebhookTask(t, 1) }) } func TestPullSquash(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - hookTasks, err := webhook.HookTasks(t.Context(), 1, 1) // Retrieve previous hook number - assert.NoError(t, err) - hookTasksLenBefore := len(hookTasks) - + preparePullMergeWebhook(t, 1) session := loginUser(t, "user1") // FIXME: don't use admin user for testing testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -223,18 +222,13 @@ func TestPullSquash(t *testing.T) { DeleteBranch: false, }) - hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) - assert.NoError(t, err) - assert.Len(t, hookTasks, hookTasksLenBefore+1) + assertPullMergeWebhookTask(t, 1) }) } func TestPullSquashWithHeadCommitID(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - hookTasks, err := webhook.HookTasks(t.Context(), 1, 1) // Retrieve previous hook number - assert.NoError(t, err) - hookTasksLenBefore := len(hookTasks) - + preparePullMergeWebhook(t, 1) session := loginUser(t, "user1") // FIXME: don't use admin user for testing testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") @@ -267,9 +261,7 @@ func TestPullSquashWithHeadCommitID(t *testing.T) { assert.Equal(t, 4, repo.NumPulls) assert.Equal(t, 3, repo.NumOpenPulls) - hookTasks, err = webhook.HookTasks(t.Context(), 1, 1) - assert.NoError(t, err) - assert.Len(t, hookTasks, hookTasksLenBefore+1) + assertPullMergeWebhookTask(t, 1) }) } @@ -303,18 +295,14 @@ func TestPullCleanUpAfterMerge(t *testing.T) { // Check PR branch deletion resp = testPullCleanUp(t, session, elem[1], elem[2], elem[4]) - respJSON := struct { - Redirect string - }{} - DecodeJSON(t, resp, &respJSON) + respJSON := test.ParseJSONRedirect(resp.Body.Bytes()) + require.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") - assert.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") - - elem = strings.Split(respJSON.Redirect, "/") + elem = strings.Split(*respJSON.Redirect, "/") assert.Equal(t, "pulls", elem[3]) // Check branch deletion result - req := NewRequest(t, "GET", respJSON.Redirect) + req := NewRequest(t, "GET", *respJSON.Redirect) resp = session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -767,8 +755,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) { // search issues searchIssuesResp := session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) - var apiIssuesBefore []*api.Issue - DecodeJSON(t, searchIssuesResp, &apiIssuesBefore) + apiIssuesBefore := DecodeJSON(t, searchIssuesResp, []*api.Issue{}) assert.Empty(t, apiIssuesBefore) // merge the pull request @@ -790,11 +777,9 @@ func TestPullMergeIndexerNotifier(t *testing.T) { // search issues again searchIssuesResp = session.MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) - var apiIssuesAfter []*api.Issue - DecodeJSON(t, searchIssuesResp, &apiIssuesAfter) - if assert.Len(t, apiIssuesAfter, 1) { - assert.Equal(t, issue.ID, apiIssuesAfter[0].ID) - } + apiIssuesAfter := DecodeJSON(t, searchIssuesResp, []*api.Issue{}) + require.Len(t, apiIssuesAfter, 1) + assert.Equal(t, issue.ID, apiIssuesAfter[0].ID) }) } diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 4b72962d4f5..12a11d45bd1 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -10,12 +10,14 @@ import ( "net/http/httptest" "net/url" "path" + "strconv" "strings" "testing" "time" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" + db_model "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -40,26 +42,28 @@ import ( func TestNewWebHookLink(t *testing.T) { defer tests.PrepareTestEnv(t)() + require.NoError(t, db_model.Insert(t.Context(), &webhook.Webhook{ + RepoID: 1, + URL: "http://localhost/gitea-test-webhook-link", + ContentType: webhook.ContentTypeJSON, + Events: `{}`, + IsActive: true, + })) + hook := unittest.AssertExistsAndLoadBean(t, &webhook.Webhook{RepoID: 1}) session := loginUser(t, "user2") - - baseurl := "/user2/repo1/settings/hooks" - tests := []string{ - // webhook list page - baseurl, - // new webhook page - baseurl + "/gitea/new", - // edit webhook page - baseurl + "/1", + webhooksBaseHref := "/user2/repo1/settings/hooks" + cases := []string{ + webhooksBaseHref, + webhooksBaseHref + "/gitea/new", + webhooksBaseHref + "/" + strconv.FormatInt(hook.ID, 10), // edit webhook } - - for _, url := range tests { - resp := session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK) + for _, reqHref := range cases { + resp := session.MakeRequest(t, NewRequest(t, "GET", reqHref), http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) menus := htmlDoc.doc.Find(".ui.top.attached.header .ui.dropdown .menu a") menus.Each(func(i int, menu *goquery.Selection) { - url, exist := menu.Attr("href") - assert.True(t, exist) - assert.True(t, strings.HasPrefix(url, baseurl)) + foundHref := menu.AttrOr("href", "") + assert.True(t, strings.HasPrefix(foundHref, webhooksBaseHref)) }) } } diff --git a/tests/test_utils.go b/tests/test_utils.go index b724a30610a..d5a8008cefb 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "code.gitea.io/gitea/models/db" @@ -15,7 +16,6 @@ import ( "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/testlogger" @@ -25,8 +25,9 @@ import ( "github.com/stretchr/testify/assert" ) -func InitTest() { +func InitTest() error { testlogger.Init() + if os.Getenv("GITEA_TEST_CONF") == "" { // By default, use sqlite.ini for testing, then IDE like GoLand can start the test process with debugger. // It's easier for developers to debug bugs step by step with a debugger. @@ -39,101 +40,102 @@ func InitTest() { setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" if err := git.InitFull(); err != nil { - log.Fatal("git.InitOnceWithSync: %v", err) + return err } setting.LoadDBSetting() if err := storage.Init(); err != nil { - testlogger.Panicf("Init storage failed: %v\n", err) + return err } switch { case setting.Database.Type.IsMySQL(): - connType := "tcp" - if len(setting.Database.Host) > 0 && setting.Database.Host[0] == '/' { // looks like a unix socket - connType = "unix" - } - - db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@%s(%s)/", - setting.Database.User, setting.Database.Passwd, connType, setting.Database.Host)) - defer db.Close() - if err != nil { - log.Fatal("sql.Open: %v", err) - } - if _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name); err != nil { - log.Fatal("db.Exec: %v", err) - } - case setting.Database.Type.IsPostgreSQL(): - var db *sql.DB - var err error - if setting.Database.Host[0] == '/' { - db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", - setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) - } else { - db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", - setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) - } - - defer db.Close() - if err != nil { - log.Fatal("sql.Open: %v", err) - } - dbrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name)) - if err != nil { - log.Fatal("db.Query: %v", err) - } - defer dbrows.Close() - - if !dbrows.Next() { - if _, err = db.Exec("CREATE DATABASE " + setting.Database.Name); err != nil { - log.Fatal("db.Exec: CREATE DATABASE: %v", err) + { + connType := util.Iif(strings.HasPrefix(setting.Database.Host, "/"), "unix", "tcp") + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@%s(%s)/", + setting.Database.User, setting.Database.Passwd, connType, setting.Database.Host)) + if err != nil { + return err + } + defer db.Close() + if _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name); err != nil { + return err } } - // Check if we need to setup a specific schema - if len(setting.Database.Schema) == 0 { - break - } - db.Close() - - if setting.Database.Host[0] == '/' { - db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", - setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) - } else { - db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", + case setting.Database.Type.IsPostgreSQL(): + openPostgreSQL := func() (*sql.DB, error) { + if strings.HasPrefix(setting.Database.Host, "/") { + return sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@/%s?sslmode=%s&host=%s", + setting.Database.User, setting.Database.Passwd, setting.Database.Name, setting.Database.SSLMode, setting.Database.Host)) + } + return sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) } - // This is a different db object; requires a different Close() - defer db.Close() - if err != nil { - log.Fatal("sql.Open: %v", err) - } - schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) - if err != nil { - log.Fatal("db.Query: %v", err) - } - defer schrows.Close() - if !schrows.Next() { - // Create and setup a DB schema - if _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema); err != nil { - log.Fatal("db.Exec: CREATE SCHEMA: %v", err) + // create database + { + db, err := openPostgreSQL() + if err != nil { + return err + } + defer db.Close() + dbRows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name)) + if err != nil { + return err + } + defer dbRows.Close() + + if !dbRows.Next() { + if _, err = db.Exec("CREATE DATABASE " + setting.Database.Name); err != nil { + return err + } + } + // Check if we need to set up a specific schema + if setting.Database.Schema == "" { + break + } + db.Close() + } + + // create schema + { + db, err := openPostgreSQL() + if err != nil { + return err + } + defer db.Close() + + schemaRows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) + if err != nil { + return err + } + defer schemaRows.Close() + + if !schemaRows.Next() { + // Create and set up a DB schema + if _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema); err != nil { + return err + } } } case setting.Database.Type.IsMSSQL(): - host, port := setting.ParseMSSQLHostPort(setting.Database.Host) - db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", - host, port, "master", setting.Database.User, setting.Database.Passwd)) - if err != nil { - log.Fatal("sql.Open: %v", err) + { + host, port := setting.ParseMSSQLHostPort(setting.Database.Host) + db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", + host, port, "master", setting.Database.User, setting.Database.Passwd)) + if err != nil { + return err + } + defer db.Close() + if _, err = db.Exec(fmt.Sprintf("If(db_id(N'%s') IS NULL) BEGIN CREATE DATABASE %s; END;", setting.Database.Name, setting.Database.Name)); err != nil { + return err + } } - if _, err := db.Exec(fmt.Sprintf("If(db_id(N'%s') IS NULL) BEGIN CREATE DATABASE %s; END;", setting.Database.Name, setting.Database.Name)); err != nil { - log.Fatal("db.Exec: %v", err) - } - defer db.Close() } routers.InitWebInstalled(graceful.GetManager().HammerContext()) + return nil } func PrepareAttachmentsStorage(t testing.TB) { @@ -154,7 +156,7 @@ func PrepareGitRepoDirectory(t testing.TB) { if !assert.NotEmpty(t, setting.RepoRootPath) { return } - assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/gitea-repositories-meta"), setting.RepoRootPath)) } func PrepareArtifactsStorage(t testing.TB) {