diff --git a/cmd/authelia-gen/cmd_root.go b/cmd/authelia-gen/cmd_root.go index 6fccc6068..b20acf2d9 100644 --- a/cmd/authelia-gen/cmd_root.go +++ b/cmd/authelia-gen/cmd_root.go @@ -61,6 +61,24 @@ func rootSubCommandsRunE(cmd *cobra.Command, args []string) (err error) { return err } + subCmds := sortCmds(cmd) + + for _, subCmd := range subCmds { + if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) { + continue + } + + rootCmd.SetArgs(rootCmdGetArgs(subCmd, args)) + + if err = rootCmd.Execute(); err != nil { + return err + } + } + + return nil +} + +func sortCmds(cmd *cobra.Command) []*cobra.Command { subCmds := cmd.Commands() switch cmd.Use { @@ -90,19 +108,7 @@ func rootSubCommandsRunE(cmd *cobra.Command, args []string) (err error) { }) } - for _, subCmd := range subCmds { - if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) { - continue - } - - rootCmd.SetArgs(rootCmdGetArgs(subCmd, args)) - - if err = rootCmd.Execute(); err != nil { - return err - } - } - - return nil + return subCmds } func resolveCmdName(cmd *cobra.Command) string { @@ -117,7 +123,7 @@ func resolveCmdName(cmd *cobra.Command) string { func rootCmdGetArgs(cmd *cobra.Command, args []string) []string { for { - if cmd == rootCmd { + if cmd == nil || cmd == rootCmd { break } diff --git a/cmd/authelia-gen/cmd_root_test.go b/cmd/authelia-gen/cmd_root_test.go new file mode 100644 index 000000000..1579364eb --- /dev/null +++ b/cmd/authelia-gen/cmd_root_test.go @@ -0,0 +1,125 @@ +package main + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolveCmdName(t *testing.T) { + testCases := []struct { + name string + have *cobra.Command + expected string + }{ + { + "ShouldResolveRootCmd", + newRootCmd(), + "authelia-gen", + }, + { + "ShouldResolveDocsCmd", + newDocsCmd(), + "docs", + }, + { + "ShouldResolveDocsSubCmd", + newRootCmd().Commands()[0].Commands()[0], + "code.keys", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, resolveCmdName(tc.have)) + }) + } +} + +func TestRootCmdGetArgs(t *testing.T) { + testCases := []struct { + name string + have func() *cobra.Command + args []string + expected []string + }{ + { + "ShouldReturnRootCmdArgs", + func() *cobra.Command { + cmd := newRootCmd() + + cmd.SetArgs([]string{"a", "b"}) + + return cmd.Commands()[0] + }, + []string{"c", "d"}, + []string{"authelia-gen", "code", "c", "d"}, + }, + { + "ShouldReturnRootCmdWithoutArgs", + func() *cobra.Command { + cmd := newRootCmd() + + return cmd.Commands()[0] + }, + nil, + []string{"authelia-gen", "code"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, rootCmdGetArgs(tc.have(), tc.args)) + }) + } +} + +func TestSortCmds(t *testing.T) { + testCases := []struct { + name string + have *cobra.Command + expected []string + }{ + { + "ShouldSortRootCmd", + newRootCmd(), + []string{"code", "commit-lint", "github", "locales", "docs"}, + }, + { + "ShouldSortDocsCmd", + newDocsCmd(), + []string{"cli", "data", "date"}, + }, + { + "ShouldSortGitHubCmd", + newGitHubCmd(), + []string{"issue-templates"}, + }, + { + "ShouldSortLocalesCmd", + newLocalesCmd(), + nil, + }, + { + "ShouldSortDocsDataCmd", + newDocsDataCmd(), + []string{"keys", "misc"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual := sortCmds(tc.have) + + n := len(tc.expected) + + require.Len(t, actual, n) + + for i := 0; i < n; i++ { + assert.Equal(t, tc.expected[i], actual[i].Use) + } + }) + } +} diff --git a/cmd/authelia-gen/helpers_test.go b/cmd/authelia-gen/helpers_test.go new file mode 100644 index 000000000..85da79835 --- /dev/null +++ b/cmd/authelia-gen/helpers_test.go @@ -0,0 +1,145 @@ +package main + +import ( + "net/mail" + "reflect" + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authelia/authelia/v4/internal/configuration/schema" +) + +func TestGetPFlagPath(t *testing.T) { + testCases := []struct { + name string + have func(t *testing.T) *pflag.FlagSet + names []string + expected string + err string + }{ + { + "ShouldFailEmptyFlagSet", + func(t *testing.T) *pflag.FlagSet { + return pflag.NewFlagSet("example", pflag.ContinueOnError) + }, + []string{"abc", "123"}, + "", + "failed to lookup flag 'abc': flag accessed but not defined: abc", + }, + { + "ShouldFailEmptyFlagNames", + func(t *testing.T) *pflag.FlagSet { + return pflag.NewFlagSet("example", pflag.ContinueOnError) + }, + nil, + "", + "no flag names", + }, + { + "ShouldLookupFlagNames", + func(t *testing.T) *pflag.FlagSet { + flagset := pflag.NewFlagSet("example", pflag.ContinueOnError) + + flagset.String("dir.one", "", "") + flagset.String("dir.two", "", "") + flagset.String("file.name", "", "") + + require.NoError(t, flagset.Parse([]string{"--dir.one=abc", "--dir.two=123", "--file.name=path.txt"})) + + return flagset + }, + []string{"dir.one", "dir.two", "file.name"}, + "abc/123/path.txt", + "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual, theError := getPFlagPath(tc.have(t), tc.names...) + + if tc.err == "" { + assert.NoError(t, theError) + assert.Equal(t, tc.expected, actual) + } else { + assert.EqualError(t, theError, tc.err) + assert.Equal(t, "", actual) + } + }) + } +} + +func TestBuildCSP(t *testing.T) { + testCases := []struct { + name string + have string + ruleSets [][]CSPValue + expected string + }{ + { + "ShouldParseDefault", + codeCSPProductionDefaultSrc, + [][]CSPValue{ + codeCSPValuesCommon, + codeCSPValuesProduction, + }, + "default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, buildCSP(tc.have, tc.ruleSets...)) + }) + } +} + +func TestContainsType(t *testing.T) { + astring := "" + + testCases := []struct { + name string + have any + expected bool + }{ + { + "ShouldContainMailAddress", + mail.Address{}, + true, + }, + { + "ShouldContainSchemaAddressPtr", + &schema.Address{}, + true, + }, + { + "ShouldNotContainString", + astring, + false, + }, + { + "ShouldNotContainStringPtr", + &astring, + false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, containsType(reflect.TypeOf(tc.have), decodedTypes)) + }) + } +} + +func TestReadTags(t *testing.T) { + assert.NotPanics(t, func() { + readTags("", reflect.TypeOf(schema.Configuration{}), false) + }) + + assert.NotPanics(t, func() { + readTags("", reflect.TypeOf(schema.Configuration{}), true) + }) +} diff --git a/cmd/authelia-gen/templates_test.go b/cmd/authelia-gen/templates_test.go new file mode 100644 index 000000000..4174570e0 --- /dev/null +++ b/cmd/authelia-gen/templates_test.go @@ -0,0 +1,13 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestShouldFailToLoadBadTemplate(t *testing.T) { + assert.Panics(t, func() { + mustLoadTmplFS("bad tmpl") + }) +} diff --git a/cmd/authelia-gen/types.go b/cmd/authelia-gen/types.go index 8170bcfc6..1a6e49992 100644 --- a/cmd/authelia-gen/types.go +++ b/cmd/authelia-gen/types.go @@ -120,6 +120,11 @@ const ( labelAreaPrefixStatus = "status" ) +type label interface { + String() string + LabelDescription() string +} + type labelPriority int //nolint:deadcode,varcheck // Kept for future use. @@ -129,6 +134,7 @@ const ( labelPriorityMedium labelPriorityNormal labelPriorityLow + labelPriorityVeryLow ) var labelPriorityDescriptions = [...]string{ @@ -137,14 +143,15 @@ var labelPriorityDescriptions = [...]string{ "Medium", "Normal", "Low", + "Very Low", } -func (p labelPriority) String() string { - return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, p+1, strings.ToLower(labelPriorityDescriptions[p])) +func (l labelPriority) String() string { + return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, l+1, labelFormatString(labelPriorityDescriptions[l])) } -func (p labelPriority) Description() string { - return labelPriorityDescriptions[p] +func (l labelPriority) LabelDescription() string { + return labelPriorityDescriptions[l] } type labelStatus int @@ -155,12 +162,16 @@ const ( ) var labelStatusDescriptions = [...]string{ - "needs-design", - "needs-triage", + "Needs Design", + "Needs Triage", } -func (s labelStatus) String() string { - return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelStatusDescriptions[s]) +func (l labelStatus) String() string { + return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelFormatString(labelStatusDescriptions[l])) +} + +func (l labelStatus) LabelDescription() string { + return labelStatusDescriptions[l] } type labelType int @@ -173,13 +184,24 @@ const ( ) var labelTypeDescriptions = [...]string{ - "feature", - "bug/unconfirmed", - "bug", + "Feature", + "Bug: Unconfirmed", + "Bug", } -func (t labelType) String() string { - return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelTypeDescriptions[t]) +func (l labelType) String() string { + return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelFormatString(labelTypeDescriptions[l])) +} + +func (l labelType) LabelDescription() string { + return labelTypeDescriptions[l] +} + +func labelFormatString(in string) string { + in = strings.ReplaceAll(in, ": ", "/") + in = strings.ReplaceAll(in, " ", "-") + + return strings.ToLower(in) } // CSPValue represents individual CSP values. diff --git a/cmd/authelia-gen/types_test.go b/cmd/authelia-gen/types_test.go new file mode 100644 index 000000000..c3ee7aaf7 --- /dev/null +++ b/cmd/authelia-gen/types_test.go @@ -0,0 +1,90 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLabels(t *testing.T) { + testCases := []struct { + name string + have label + expectedDescription string + expectedString string + }{ + { + "ShouldShowCorrectPriorityCriticalValues", + labelPriorityCritical, + "Critical", + "priority/1/critical", + }, + { + "ShouldShowCorrectPriorityHighValues", + labelPriorityHigh, + "High", + "priority/2/high", + }, + { + "ShouldShowCorrectPriorityMediumValues", + labelPriorityMedium, + "Medium", + "priority/3/medium", + }, + { + "ShouldShowCorrectPriorityNormalValues", + labelPriorityNormal, + "Normal", + "priority/4/normal", + }, + { + "ShouldShowCorrectPriorityLowValues", + labelPriorityLow, + "Low", + "priority/5/low", + }, + { + "ShouldShowCorrectPriorityVeryLowValues", + labelPriorityVeryLow, + "Very Low", + "priority/6/very-low", + }, + { + "ShouldShowCorrectStatusNeedsDesignValues", + labelStatusNeedsDesign, + "Needs Design", + "status/needs-design", + }, + { + "ShouldShowCorrectStatusNeedsTriageValues", + labelStatusNeedsTriage, + "Needs Triage", + "status/needs-triage", + }, + { + "ShouldShowCorrectTypeFeatureValues", + labelTypeFeature, + "Feature", + "type/feature", + }, + { + "ShouldShowCorrectTypeBugUnconfirmedValues", + labelTypeBugUnconfirmed, + "Bug: Unconfirmed", + "type/bug/unconfirmed", + }, + { + "ShouldShowCorrectTypeBugValues", + labelTypeBug, + "Bug", + "type/bug", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expectedString, tc.have.String()) + assert.Equal(t, tc.expectedDescription, tc.have.LabelDescription()) + }) + } +}