From afd5ffef69f351c1697fc4c56187002db4d4ed4b Mon Sep 17 00:00:00 2001 From: Branislav Lazic Date: Thu, 26 Sep 2024 19:17:04 +0200 Subject: [PATCH] Add tests --- .gitignore | 3 +- .mockery.yaml | 4 + Makefile | 2 +- doc.go | 3 + filewriter/writer.go | 37 -------- generator/app.go | 17 ++-- generator/app_test.go | 63 ++++++++++++++ go.mod | 5 ++ go.sum | 9 ++ main.go | 3 +- .../codeengio/idi/writer/mock_Writer.go | 86 +++++++++++++++++++ tests/data/main.go.tmpl | 7 ++ tests/fs.go | 13 +++ writer/fs.go | 46 ++++++++++ writer/fs_test.go | 43 ++++++++++ writer/writer.go | 7 ++ 16 files changed, 302 insertions(+), 46 deletions(-) create mode 100644 .mockery.yaml create mode 100644 doc.go delete mode 100644 filewriter/writer.go create mode 100644 generator/app_test.go create mode 100644 mocks/github.com/codeengio/idi/writer/mock_Writer.go create mode 100644 tests/data/main.go.tmpl create mode 100644 tests/fs.go create mode 100644 writer/fs.go create mode 100644 writer/fs_test.go create mode 100644 writer/writer.go diff --git a/.gitignore b/.gitignore index 5be39a7..9ff7556 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin release .vscode -.idea \ No newline at end of file +.idea +out \ No newline at end of file diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..af7879b --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,4 @@ +packages: + github.com/codeengio/idi/writer: + config: + all: True \ No newline at end of file diff --git a/Makefile b/Makefile index f4c404d..2821d6e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ APP=idi MODULE := github.com/codeengio/idi -VERSION := v0.1 +VERSION := 1.0.0 .PHONY: clean bin test diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..d149bae --- /dev/null +++ b/doc.go @@ -0,0 +1,3 @@ +package main + +//go:generate mockery --all --log-level=debug --disable-version-string --inpackage diff --git a/filewriter/writer.go b/filewriter/writer.go deleted file mode 100644 index 6d8bc87..0000000 --- a/filewriter/writer.go +++ /dev/null @@ -1,37 +0,0 @@ -package filewriter - -import ( - "embed" - "fmt" - "html/template" - "log/slog" - "os" - "path/filepath" -) - -func WriteFile(appName, templateName, outFileName string, templateFS embed.FS, args map[string]string) error { - tmpl, err := template.ParseFS(templateFS, templateName) - if err != nil { - slog.Error(err.Error()) - return err - } - - fp := fmt.Sprintf("%s/%s", appName, outFileName) - if err := os.MkdirAll(filepath.Dir(fp), 0770); err != nil { - return err - } - - f, err := os.Create(fp) - if err != nil { - slog.Error(err.Error()) - return err - } - - err = tmpl.Execute(f, args) - if err != nil { - slog.Error(err.Error()) - return err - } - - return err -} diff --git a/generator/app.go b/generator/app.go index c9a288e..95b6850 100644 --- a/generator/app.go +++ b/generator/app.go @@ -1,20 +1,25 @@ package generator import ( - "embed" + "io/fs" - "github.com/codeengio/idi/filewriter" + "github.com/codeengio/idi/writer" "github.com/rs/zerolog" ) type App struct { - Logger zerolog.Logger + logger zerolog.Logger + writer writer.Writer } -func (f *App) GenerateNew(name, goModule string, templatesMap map[string]string, templateFS embed.FS) error { +func NewApp(logger zerolog.Logger, w writer.Writer) *App { + return &App{logger: logger, writer: w} +} + +func (f *App) GenerateNew(name, goModule string, templatesMap map[string]string, templateFS fs.FS) error { for fileName, templatePath := range templatesMap { - f.Logger.Info().Str("create", fileName).Msg("generating file") - err := filewriter.WriteFile( + f.logger.Info().Str("create", fileName).Msg("generating file") + err := f.writer.WriteTemplate( name, templatePath, fileName, diff --git a/generator/app_test.go b/generator/app_test.go new file mode 100644 index 0000000..1e9da86 --- /dev/null +++ b/generator/app_test.go @@ -0,0 +1,63 @@ +package generator + +import ( + "errors" + "github.com/codeengio/idi/mocks/github.com/codeengio/idi/writer" + "github.com/codeengio/idi/tests" + "github.com/rs/zerolog" + "github.com/stretchr/testify/mock" + "testing" +) + +func TestApp_GenerateNew(t *testing.T) { + w := &writer.MockWriter{} + + w.On("WriteTemplate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) + + appGen := NewApp(zerolog.Logger{}, w) + err := appGen.GenerateNew( + "app", + "example.com/module/app", + map[string]string{"README.md": "templates/readme.md.tmpl"}, + tests.GetFS(), + ) + if err != nil { + t.Error(err) + } + + if len(w.Calls) != 1 { + t.Fatalf("expected 1 call, got=%d", len(w.Calls)) + } +} + +func TestApp_GenerateNew_EmptyTemplates(t *testing.T) { + w := &writer.MockWriter{} + appGen := NewApp(zerolog.Logger{}, w) + err := appGen.GenerateNew("app", "example.com/module/app", map[string]string{}, tests.GetFS()) + if err != nil { + t.Error(err) + } + + if len(w.Calls) > 0 { + t.Fatalf("should not have any calls") + } +} + +func TestApp_GenerateNew_WriteError(t *testing.T) { + w := &writer.MockWriter{} + + w.On("WriteTemplate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(errors.New("write error")) + + appGen := NewApp(zerolog.Logger{}, w) + err := appGen.GenerateNew( + "app", + "example.com/module/app", + map[string]string{"README.md": "templates/readme.md.tmpl"}, + tests.GetFS(), + ) + if err == nil { + t.Fatalf("should have errored") + } +} diff --git a/go.mod b/go.mod index 569b7d8..9cafd11 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,14 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect golang.org/x/sys v0.25.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3ca3d11..160be08 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -10,6 +12,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -18,10 +22,15 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 0271842..00791a9 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "embed" + "github.com/codeengio/idi/writer" "os" "time" @@ -57,7 +58,7 @@ func runNewApp(logger zerolog.Logger) func(*cobra.Command, []string) error { return err } - appGen := generator.App{Logger: logger} + appGen := generator.NewApp(logger, writer.NewFS(logger)) err = appGen.GenerateNew(name, module, templates, templateFS) if err != nil { logger.Error().Err(err).Msg("failed to generate new app") diff --git a/mocks/github.com/codeengio/idi/writer/mock_Writer.go b/mocks/github.com/codeengio/idi/writer/mock_Writer.go new file mode 100644 index 0000000..3958105 --- /dev/null +++ b/mocks/github.com/codeengio/idi/writer/mock_Writer.go @@ -0,0 +1,86 @@ +// Code generated by mockery. DO NOT EDIT. + +package writer + +import ( + fs "io/fs" + + mock "github.com/stretchr/testify/mock" +) + +// MockWriter is an autogenerated mock type for the Writer type +type MockWriter struct { + mock.Mock +} + +type MockWriter_Expecter struct { + mock *mock.Mock +} + +func (_m *MockWriter) EXPECT() *MockWriter_Expecter { + return &MockWriter_Expecter{mock: &_m.Mock} +} + +// WriteTemplate provides a mock function with given fields: appDir, templateName, outFileName, templateFS, args +func (_m *MockWriter) WriteTemplate(appDir string, templateName string, outFileName string, templateFS fs.FS, args map[string]string) error { + ret := _m.Called(appDir, templateName, outFileName, templateFS, args) + + if len(ret) == 0 { + panic("no return value specified for WriteTemplate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, string, string, fs.FS, map[string]string) error); ok { + r0 = rf(appDir, templateName, outFileName, templateFS, args) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockWriter_WriteTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteTemplate' +type MockWriter_WriteTemplate_Call struct { + *mock.Call +} + +// WriteTemplate is a helper method to define mock.On call +// - appDir string +// - templateName string +// - outFileName string +// - templateFS fs.FS +// - args map[string]string +func (_e *MockWriter_Expecter) WriteTemplate(appDir interface{}, templateName interface{}, outFileName interface{}, templateFS interface{}, args interface{}) *MockWriter_WriteTemplate_Call { + return &MockWriter_WriteTemplate_Call{Call: _e.mock.On("WriteTemplate", appDir, templateName, outFileName, templateFS, args)} +} + +func (_c *MockWriter_WriteTemplate_Call) Run(run func(appDir string, templateName string, outFileName string, templateFS fs.FS, args map[string]string)) *MockWriter_WriteTemplate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string), args[2].(string), args[3].(fs.FS), args[4].(map[string]string)) + }) + return _c +} + +func (_c *MockWriter_WriteTemplate_Call) Return(_a0 error) *MockWriter_WriteTemplate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockWriter_WriteTemplate_Call) RunAndReturn(run func(string, string, string, fs.FS, map[string]string) error) *MockWriter_WriteTemplate_Call { + _c.Call.Return(run) + return _c +} + +// NewMockWriter creates a new instance of MockWriter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockWriter(t interface { + mock.TestingT + Cleanup(func()) +}) *MockWriter { + mock := &MockWriter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/tests/data/main.go.tmpl b/tests/data/main.go.tmpl new file mode 100644 index 0000000..dd7d5cd --- /dev/null +++ b/tests/data/main.go.tmpl @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("{{.AppName}}") +} diff --git a/tests/fs.go b/tests/fs.go new file mode 100644 index 0000000..775e0ab --- /dev/null +++ b/tests/fs.go @@ -0,0 +1,13 @@ +package tests + +import ( + "embed" + "io/fs" +) + +//go:embed data/* +var data embed.FS + +var GetFS = func() fs.FS { + return data +} diff --git a/writer/fs.go b/writer/fs.go new file mode 100644 index 0000000..241993c --- /dev/null +++ b/writer/fs.go @@ -0,0 +1,46 @@ +package writer + +import ( + "fmt" + "github.com/rs/zerolog" + "html/template" + "io/fs" + "os" + "path/filepath" +) + +type FS struct { + logger zerolog.Logger +} + +func NewFS(logger zerolog.Logger) *FS { + return &FS{logger: logger} +} + +func (fs *FS) WriteTemplate(appDir, templateName, outFileName string, templateFS fs.FS, args map[string]string) error { + tmpl, err := template.ParseFS(templateFS, templateName) + if err != nil { + fs.logger.Error().Err(err).Msg("error parsing template") + return err + } + + fp := fmt.Sprintf("%s/%s", appDir, outFileName) + if err := os.MkdirAll(filepath.Dir(fp), 0770); err != nil { + fs.logger.Error().Err(err).Msg("error creating directory") + return err + } + + f, err := os.Create(fp) + if err != nil { + fs.logger.Error().Err(err).Msg("error creating file") + return err + } + + err = tmpl.Execute(f, args) + if err != nil { + fs.logger.Error().Err(err).Msg("error executing template") + return err + } + + return err +} diff --git a/writer/fs_test.go b/writer/fs_test.go new file mode 100644 index 0000000..4ebb303 --- /dev/null +++ b/writer/fs_test.go @@ -0,0 +1,43 @@ +package writer + +import ( + "github.com/codeengio/idi/tests" + "github.com/rs/zerolog" + "os" + "testing" +) + +func TestFS_WriteTemplate(t *testing.T) { + fs := NewFS(zerolog.Logger{}) + err := fs.WriteTemplate( + "../out/app", + "data/main.go.tmpl", + "main.go", + tests.GetFS(), + map[string]string{"AppName": "app"}, + ) + if err != nil { + t.Error(err) + } + stat, err := os.Stat("../out/app/main.go") + if err != nil { + t.Error(err) + } + if stat.Name() != "main.go" { + t.Fatalf("expected file name=%s. got=%s", "main.go", stat.Name()) + } +} + +func TestFS_WriteTemplate_NotExists(t *testing.T) { + fs := NewFS(zerolog.Logger{}) + err := fs.WriteTemplate( + "../out/app", + "data/xyz.go.tmpl", + "main.go", + tests.GetFS(), + map[string]string{"AppName": "app"}, + ) + if err == nil { + t.Fatalf("expected error. got=nil") + } +} diff --git a/writer/writer.go b/writer/writer.go new file mode 100644 index 0000000..b599131 --- /dev/null +++ b/writer/writer.go @@ -0,0 +1,7 @@ +package writer + +import "io/fs" + +type Writer interface { + WriteTemplate(appDir, templateName, outFileName string, templateFS fs.FS, args map[string]string) error +}