package config import ( "context" "os" "testing" "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" cmd "codeberg.org/codeberg/pages/cli" ) func runApp(t *testing.T, fn func(*cli.Context) error, args []string) { app := cmd.CreatePagesApp() app.Action = fn appCtx, appCancel := context.WithCancel(context.Background()) defer appCancel() // os.Args always contains the binary name args = append([]string{"testing"}, args...) err := app.RunContext(appCtx, args) assert.NoError(t, err) } // fixArrayFromCtx fixes the number of "changed" strings in a string slice according to the number of values in the context. // This is a workaround because the cli library has a bug where the number of values in the context gets bigger the more tests are run. func fixArrayFromCtx(ctx *cli.Context, key string, expected []string) []string { if ctx.IsSet(key) { ctxSlice := ctx.StringSlice(key) if len(ctxSlice) > 1 { for i := 1; i < len(ctxSlice); i++ { expected = append([]string{"changed"}, expected...) } } } return expected } func readTestConfig() (*Config, error) { content, err := os.ReadFile("assets/test_config.toml") if err != nil { return nil, err } expectedConfig := NewDefaultConfig() err = toml.Unmarshal(content, &expectedConfig) if err != nil { return nil, err } return &expectedConfig, nil } func TestReadConfigShouldReturnEmptyConfigWhenConfigArgEmpty(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg, err := ReadConfig(ctx) expected := NewDefaultConfig() assert.Equal(t, &expected, cfg) return err }, []string{}, ) } func TestReadConfigShouldReturnConfigFromFileWhenConfigArgPresent(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg, err := ReadConfig(ctx) if err != nil { return err } expectedConfig, err := readTestConfig() if err != nil { return err } assert.Equal(t, expectedConfig, cfg) return nil }, []string{"--config-file", "assets/test_config.toml"}, ) } func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg, err := ReadConfig(ctx) if err != nil { return err } MergeConfig(ctx, cfg) expectedConfig, err := readTestConfig() if err != nil { return err } expectedConfig.LogLevel = "debug" expectedConfig.Forge.Root = "not-codeberg.org" expectedConfig.ACME.AcceptTerms = true expectedConfig.Server.Host = "172.17.0.2" expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...) assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--config-file", "assets/test_config.toml", "--log-level", "debug", "--forge-root", "not-codeberg.org", "--acme-accept-terms", "--host", "172.17.0.2", }, ) } func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg := &Config{ LogLevel: "original", Server: ServerConfig{ Host: "original", Port: 8080, HttpPort: 80, HttpServerEnabled: false, MainDomain: "original", RawDomain: "original", PagesBranches: []string{"original"}, AllowedCorsDomains: []string{"original"}, BlacklistedPaths: []string{"original"}, }, Forge: ForgeConfig{ Root: "original", Token: "original", LFSEnabled: false, FollowSymlinks: false, DefaultMimeType: "original", ForbiddenMimeTypes: []string{"original"}, }, Database: DatabaseConfig{ Type: "original", Conn: "original", }, ACME: ACMEConfig{ Email: "original", APIEndpoint: "original", AcceptTerms: false, UseRateLimits: false, EAB_HMAC: "original", EAB_KID: "original", DNSProvider: "original", NoDNS01: false, AccountConfigFile: "original", }, } MergeConfig(ctx, cfg) expectedConfig := &Config{ LogLevel: "changed", Server: ServerConfig{ Host: "changed", Port: 8443, HttpPort: 443, HttpServerEnabled: true, MainDomain: "changed", RawDomain: "changed", PagesBranches: []string{"changed"}, AllowedCorsDomains: []string{"changed"}, BlacklistedPaths: append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...), }, Forge: ForgeConfig{ Root: "changed", Token: "changed", LFSEnabled: true, FollowSymlinks: true, DefaultMimeType: "changed", ForbiddenMimeTypes: []string{"changed"}, }, Database: DatabaseConfig{ Type: "changed", Conn: "changed", }, ACME: ACMEConfig{ Email: "changed", APIEndpoint: "changed", AcceptTerms: true, UseRateLimits: true, EAB_HMAC: "changed", EAB_KID: "changed", DNSProvider: "changed", NoDNS01: true, AccountConfigFile: "changed", }, } assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--log-level", "changed", // Server "--pages-domain", "changed", "--raw-domain", "changed", "--allowed-cors-domains", "changed", "--blacklisted-paths", "changed", "--pages-branch", "changed", "--host", "changed", "--port", "8443", "--http-port", "443", "--enable-http-server", // Forge "--forge-root", "changed", "--forge-api-token", "changed", "--enable-lfs-support", "--enable-symlink-support", "--default-mime-type", "changed", "--forbidden-mime-types", "changed", // Database "--db-type", "changed", "--db-conn", "changed", // ACME "--acme-email", "changed", "--acme-api-endpoint", "changed", "--acme-accept-terms", "--acme-use-rate-limits", "--acme-eab-hmac", "changed", "--acme-eab-kid", "changed", "--dns-provider", "changed", "--no-dns-01", "--acme-account-config", "changed", }, ) } func TestMergeServerConfigShouldAddDefaultBlacklistedPathsToBlacklistedPaths(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg := &ServerConfig{} mergeServerConfig(ctx, cfg) expected := ALWAYS_BLACKLISTED_PATHS assert.Equal(t, expected, cfg.BlacklistedPaths) return nil }, []string{}, ) } func TestMergeServerConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { for range []uint8{0, 1} { runApp( t, func(ctx *cli.Context) error { cfg := &ServerConfig{ Host: "original", Port: 8080, HttpPort: 80, HttpServerEnabled: false, MainDomain: "original", RawDomain: "original", AllowedCorsDomains: []string{"original"}, BlacklistedPaths: []string{"original"}, } mergeServerConfig(ctx, cfg) expectedConfig := &ServerConfig{ Host: "changed", Port: 8443, HttpPort: 443, HttpServerEnabled: true, MainDomain: "changed", RawDomain: "changed", AllowedCorsDomains: fixArrayFromCtx(ctx, "allowed-cors-domains", []string{"changed"}), BlacklistedPaths: fixArrayFromCtx(ctx, "blacklisted-paths", append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...)), } assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--pages-domain", "changed", "--raw-domain", "changed", "--allowed-cors-domains", "changed", "--blacklisted-paths", "changed", "--host", "changed", "--port", "8443", "--http-port", "443", "--enable-http-server", }, ) } } func TestMergeServerConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) { type testValuePair struct { args []string callback func(*ServerConfig) } testValuePairs := []testValuePair{ {args: []string{"--host", "changed"}, callback: func(sc *ServerConfig) { sc.Host = "changed" }}, {args: []string{"--port", "8443"}, callback: func(sc *ServerConfig) { sc.Port = 8443 }}, {args: []string{"--http-port", "443"}, callback: func(sc *ServerConfig) { sc.HttpPort = 443 }}, {args: []string{"--enable-http-server"}, callback: func(sc *ServerConfig) { sc.HttpServerEnabled = true }}, {args: []string{"--pages-domain", "changed"}, callback: func(sc *ServerConfig) { sc.MainDomain = "changed" }}, {args: []string{"--raw-domain", "changed"}, callback: func(sc *ServerConfig) { sc.RawDomain = "changed" }}, {args: []string{"--pages-branch", "changed"}, callback: func(sc *ServerConfig) { sc.PagesBranches = []string{"changed"} }}, {args: []string{"--allowed-cors-domains", "changed"}, callback: func(sc *ServerConfig) { sc.AllowedCorsDomains = []string{"changed"} }}, {args: []string{"--blacklisted-paths", "changed"}, callback: func(sc *ServerConfig) { sc.BlacklistedPaths = []string{"changed"} }}, } for _, pair := range testValuePairs { runApp( t, func(ctx *cli.Context) error { cfg := ServerConfig{ Host: "original", Port: 8080, HttpPort: 80, HttpServerEnabled: false, MainDomain: "original", RawDomain: "original", PagesBranches: []string{"original"}, AllowedCorsDomains: []string{"original"}, BlacklistedPaths: []string{"original"}, } expectedConfig := cfg pair.callback(&expectedConfig) expectedConfig.BlacklistedPaths = append(expectedConfig.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...) expectedConfig.PagesBranches = fixArrayFromCtx(ctx, "pages-branch", expectedConfig.PagesBranches) expectedConfig.AllowedCorsDomains = fixArrayFromCtx(ctx, "allowed-cors-domains", expectedConfig.AllowedCorsDomains) expectedConfig.BlacklistedPaths = fixArrayFromCtx(ctx, "blacklisted-paths", expectedConfig.BlacklistedPaths) mergeServerConfig(ctx, &cfg) assert.Equal(t, expectedConfig, cfg) return nil }, pair.args, ) } } func TestMergeForgeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg := &ForgeConfig{ Root: "original", Token: "original", LFSEnabled: false, FollowSymlinks: false, DefaultMimeType: "original", ForbiddenMimeTypes: []string{"original"}, } mergeForgeConfig(ctx, cfg) expectedConfig := &ForgeConfig{ Root: "changed", Token: "changed", LFSEnabled: true, FollowSymlinks: true, DefaultMimeType: "changed", ForbiddenMimeTypes: fixArrayFromCtx(ctx, "forbidden-mime-types", []string{"changed"}), } assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--forge-root", "changed", "--forge-api-token", "changed", "--enable-lfs-support", "--enable-symlink-support", "--default-mime-type", "changed", "--forbidden-mime-types", "changed", }, ) } func TestMergeForgeConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) { type testValuePair struct { args []string callback func(*ForgeConfig) } testValuePairs := []testValuePair{ {args: []string{"--forge-root", "changed"}, callback: func(gc *ForgeConfig) { gc.Root = "changed" }}, {args: []string{"--forge-api-token", "changed"}, callback: func(gc *ForgeConfig) { gc.Token = "changed" }}, {args: []string{"--enable-lfs-support"}, callback: func(gc *ForgeConfig) { gc.LFSEnabled = true }}, {args: []string{"--enable-symlink-support"}, callback: func(gc *ForgeConfig) { gc.FollowSymlinks = true }}, {args: []string{"--default-mime-type", "changed"}, callback: func(gc *ForgeConfig) { gc.DefaultMimeType = "changed" }}, {args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *ForgeConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }}, } for _, pair := range testValuePairs { runApp( t, func(ctx *cli.Context) error { cfg := ForgeConfig{ Root: "original", Token: "original", LFSEnabled: false, FollowSymlinks: false, DefaultMimeType: "original", ForbiddenMimeTypes: []string{"original"}, } expectedConfig := cfg pair.callback(&expectedConfig) mergeForgeConfig(ctx, &cfg) expectedConfig.ForbiddenMimeTypes = fixArrayFromCtx(ctx, "forbidden-mime-types", expectedConfig.ForbiddenMimeTypes) assert.Equal(t, expectedConfig, cfg) return nil }, pair.args, ) } } func TestMergeForgeConfigShouldReplaceValuesGivenGiteaOptionsExist(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg := &ForgeConfig{ Root: "original", Token: "original", } mergeForgeConfig(ctx, cfg) expectedConfig := &ForgeConfig{ Root: "changed", Token: "changed", } assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--gitea-root", "changed", "--gitea-api-token", "changed", }, ) } func TestMergeDatabaseConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg := &DatabaseConfig{ Type: "original", Conn: "original", } mergeDatabaseConfig(ctx, cfg) expectedConfig := &DatabaseConfig{ Type: "changed", Conn: "changed", } assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--db-type", "changed", "--db-conn", "changed", }, ) } func TestMergeDatabaseConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) { type testValuePair struct { args []string callback func(*DatabaseConfig) } testValuePairs := []testValuePair{ {args: []string{"--db-type", "changed"}, callback: func(gc *DatabaseConfig) { gc.Type = "changed" }}, {args: []string{"--db-conn", "changed"}, callback: func(gc *DatabaseConfig) { gc.Conn = "changed" }}, } for _, pair := range testValuePairs { runApp( t, func(ctx *cli.Context) error { cfg := DatabaseConfig{ Type: "original", Conn: "original", } expectedConfig := cfg pair.callback(&expectedConfig) mergeDatabaseConfig(ctx, &cfg) assert.Equal(t, expectedConfig, cfg) return nil }, pair.args, ) } } func TestMergeACMEConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { runApp( t, func(ctx *cli.Context) error { cfg := &ACMEConfig{ Email: "original", APIEndpoint: "original", AcceptTerms: false, UseRateLimits: false, EAB_HMAC: "original", EAB_KID: "original", DNSProvider: "original", NoDNS01: false, AccountConfigFile: "original", } mergeACMEConfig(ctx, cfg) expectedConfig := &ACMEConfig{ Email: "changed", APIEndpoint: "changed", AcceptTerms: true, UseRateLimits: true, EAB_HMAC: "changed", EAB_KID: "changed", DNSProvider: "changed", NoDNS01: true, AccountConfigFile: "changed", } assert.Equal(t, expectedConfig, cfg) return nil }, []string{ "--acme-email", "changed", "--acme-api-endpoint", "changed", "--acme-accept-terms", "--acme-use-rate-limits", "--acme-eab-hmac", "changed", "--acme-eab-kid", "changed", "--dns-provider", "changed", "--no-dns-01", "--acme-account-config", "changed", }, ) } func TestMergeACMEConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) { type testValuePair struct { args []string callback func(*ACMEConfig) } testValuePairs := []testValuePair{ {args: []string{"--acme-email", "changed"}, callback: func(gc *ACMEConfig) { gc.Email = "changed" }}, {args: []string{"--acme-api-endpoint", "changed"}, callback: func(gc *ACMEConfig) { gc.APIEndpoint = "changed" }}, {args: []string{"--acme-accept-terms"}, callback: func(gc *ACMEConfig) { gc.AcceptTerms = true }}, {args: []string{"--acme-use-rate-limits"}, callback: func(gc *ACMEConfig) { gc.UseRateLimits = true }}, {args: []string{"--acme-eab-hmac", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_HMAC = "changed" }}, {args: []string{"--acme-eab-kid", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_KID = "changed" }}, {args: []string{"--dns-provider", "changed"}, callback: func(gc *ACMEConfig) { gc.DNSProvider = "changed" }}, {args: []string{"--no-dns-01"}, callback: func(gc *ACMEConfig) { gc.NoDNS01 = true }}, {args: []string{"--acme-account-config", "changed"}, callback: func(gc *ACMEConfig) { gc.AccountConfigFile = "changed" }}, } for _, pair := range testValuePairs { runApp( t, func(ctx *cli.Context) error { cfg := ACMEConfig{ Email: "original", APIEndpoint: "original", AcceptTerms: false, UseRateLimits: false, EAB_HMAC: "original", EAB_KID: "original", DNSProvider: "original", AccountConfigFile: "original", } expectedConfig := cfg pair.callback(&expectedConfig) mergeACMEConfig(ctx, &cfg) assert.Equal(t, expectedConfig, cfg) return nil }, pair.args, ) } }