diff --git a/internal/app/analytics/analytics_test.go b/internal/app/analytics/analytics_test.go new file mode 100644 index 0000000..d26d8e3 --- /dev/null +++ b/internal/app/analytics/analytics_test.go @@ -0,0 +1,183 @@ +package analytics + +import ( + "flag" + "os" + "path/filepath" + "testing" + + ga "google.golang.org/api/analyticsreporting/v4" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestFmtDate(t *testing.T) { + t.Parallel() + assert.Equal(t, "2024-01-15", fmtDate("20240115")) + assert.Equal(t, "0001-01-01", fmtDate("invalid")) +} + +func TestUcFirst(t *testing.T) { + t.Parallel() + + tests := []struct { + input string + expected string + }{ + {"sessions", "Sessions"}, + {"pageviews", "Pageviews"}, + {"users", "Users"}, + {"", ""}, + } + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + assert.Equal(t, tt.expected, ucFirst(tt.input)) + }) + } +} + +func TestRemoveGAprefix(t *testing.T) { + t.Parallel() + assert.Equal(t, "date", removeGAprefix("ga:date")) + assert.Equal(t, "sessions", removeGAprefix("ga:sessions")) + assert.Equal(t, "noprefix", removeGAprefix("noprefix")) +} + +func TestMetricName(t *testing.T) { + t.Parallel() + + entry := &ga.MetricHeaderEntry{Name: "ga:sessions"} + assert.Equal(t, "ga:sessions", metricName(entry)) +} + +func TestProcessReportHeader(t *testing.T) { + t.Parallel() + + res := &ga.GetReportsResponse{ + Reports: []*ga.Report{{ + ColumnHeader: &ga.ColumnHeader{ + Dimensions: []string{"ga:date"}, + MetricHeader: &ga.MetricHeader{ + MetricHeaderEntries: []*ga.MetricHeaderEntry{ + {Name: "ga:sessions"}, + {Name: "ga:pageviews"}, + {Name: "ga:users"}, + }, + }, + }, + }}, + } + result := processReportHeader(res) + assert.Equal(t, []string{"date", "Sessions", "Pageviews", "Users"}, result) +} + +func TestGenerateReportRequest(t *testing.T) { + t.Parallel() + + t.Run("with default dates", func(t *testing.T) { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("view-id", "12345", "") + flagSet.String("start-date", "2024-01-01", "") + flagSet.String("end-date", "", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "view-id"}, + cli.StringFlag{Name: "start-date"}, + cli.StringFlag{Name: "end-date"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + req := generateReportRequest(ctx) + assert.Equal(t, "12345", req.ViewId) + assert.Len(t, req.DateRanges, 1) + assert.Equal(t, "2024-01-01", req.DateRanges[0].StartDate) + assert.NotEmpty(t, req.DateRanges[0].EndDate) + }) + + t.Run("with custom end date", func(t *testing.T) { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("view-id", "12345", "") + flagSet.String("start-date", "2024-01-01", "") + flagSet.String("end-date", "2024-06-30", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "view-id"}, + cli.StringFlag{Name: "start-date"}, + cli.StringFlag{Name: "end-date"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + req := generateReportRequest(ctx) + assert.Equal(t, "2024-06-30", req.DateRanges[0].EndDate) + }) +} + +func TestWriteOutput(t *testing.T) { + t.Parallel() + + res := &ga.GetReportsResponse{ + Reports: []*ga.Report{{ + ColumnHeader: &ga.ColumnHeader{ + Dimensions: []string{"ga:date"}, + MetricHeader: &ga.MetricHeader{ + MetricHeaderEntries: []*ga.MetricHeaderEntry{ + {Name: "ga:sessions"}, + }, + }, + }, + Data: &ga.ReportData{ + Rows: []*ga.ReportRow{ + { + Dimensions: []string{"20240101"}, + Metrics: []*ga.DateRangeValues{{Values: []string{"100"}}}, + }, + { + Dimensions: []string{"20240102"}, + Metrics: []*ga.DateRangeValues{{Values: []string{"200"}}}, + }, + }, + }, + }}, + } + + t.Run("write to file", func(t *testing.T) { + tmpDir := t.TempDir() + outFile := filepath.Join(tmpDir, "output.csv") + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("output", outFile, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{cli.StringFlag{Name: "output"}} + + ctx := cli.NewContext(app, flagSet, nil) + err := writeOutput(ctx, res) + require.NoError(t, err) + + data, err := os.ReadFile(outFile) + require.NoError(t, err) + assert.Contains(t, string(data), "date,Sessions") + assert.Contains(t, string(data), "2024-01-01,100") + assert.Contains(t, string(data), "2024-01-02,200") + }) + + t.Run("write to tmp file", func(t *testing.T) { + tmpDir := t.TempDir() + outFile := filepath.Join(tmpDir, "report.csv") + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("output", outFile, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{cli.StringFlag{Name: "output"}} + + ctx := cli.NewContext(app, flagSet, nil) + err := writeOutput(ctx, res) + require.NoError(t, err) + assert.FileExists(t, outFile) + }) +} diff --git a/internal/app/comment/ontology_extra_test.go b/internal/app/comment/ontology_extra_test.go new file mode 100644 index 0000000..68ebe67 --- /dev/null +++ b/internal/app/comment/ontology_extra_test.go @@ -0,0 +1,193 @@ +package comment + +import ( + "flag" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestReadHTMLContent(t *testing.T) { + t.Parallel() + + t.Run("existing file", func(t *testing.T) { + tmpDir := t.TempDir() + htmlFile := filepath.Join(tmpDir, "report.html") + content := "test" + require.NoError(t, os.WriteFile(htmlFile, []byte(content), 0o600)) + + result, err := readHTMLContent(htmlFile) + require.NoError(t, err) + assert.Equal(t, content, result) + }) + + t.Run("nonexistent file", func(t *testing.T) { + result, err := readHTMLContent("/nonexistent/file.html") + require.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("directory instead of file", func(t *testing.T) { + tmpDir := t.TempDir() + _, err := readHTMLContent(tmpDir) + assert.Error(t, err) + }) +} + +func TestReportStatusError(t *testing.T) { + t.Parallel() + + t.Run("with failures", func(t *testing.T) { + data := map[string][]*reportContent{ + "fail": {{Name: "dicty_pheno.obo"}}, + } + err := reportStatusError(data) + assert.Error(t, err) + }) + + t.Run("no failures", func(t *testing.T) { + data := map[string][]*reportContent{ + "pass": {{Name: "dicty_assay.obo"}}, + } + err := reportStatusError(data) + assert.NoError(t, err) + }) + + t.Run("empty", func(t *testing.T) { + data := map[string][]*reportContent{} + err := reportStatusError(data) + assert.NoError(t, err) + }) +} + +func TestBaseNoSuffix(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + expected string + }{ + { + name: "obo file", + input: "/onto/dicty_assay.obo", + expected: "dicty_assay", + }, + { + name: "simple filename", + input: "dicty_flower.obo", + expected: "dicty_flower", + }, + { + name: "multiple dots", + input: "/path/to/dicty_pheno.v1.obo", + expected: "dicty_pheno", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, baseNoSuffix(tt.input)) + }) + } +} + +func TestOntoReport(t *testing.T) { + t.Parallel() + + t.Run("with fail and pass", func(t *testing.T) { + reportDir := t.TempDir() + + failJSON := `[{"level": "ERROR", "violations": [{"missing_title": [{"subject": "test"}]}]}]` + require.NoError(t, + os.WriteFile( + filepath.Join(reportDir, "dicty_pheno.json"), + []byte(failJSON), + 0o600, + ), + ) + require.NoError(t, + os.WriteFile( + filepath.Join(reportDir, "dicty_pheno.html"), + []byte("fail"), + 0o600, + ), + ) + + passJSON := `[{"level": "WARN", "violations": [{"missing_label": [{"subject": "test"}]}]}]` //nolint:gosec + require.NoError(t, + os.WriteFile( + filepath.Join(reportDir, "dicty_assay.json"), + []byte(passJSON), + 0o600, + ), + ) + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("report-dir", reportDir, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{cli.StringFlag{Name: "report-dir"}} + + ctx := cli.NewContext(app, flagSet, nil) + result, err := ontoReport(ctx, []string{"dicty_pheno", "dicty_assay"}) + require.NoError(t, err) + assert.Contains(t, result, "fail") + assert.Contains(t, result, "pass") + assert.Len(t, result["fail"], 1) + assert.Len(t, result["pass"], 1) + assert.Equal(t, "dicty_pheno.obo", result["fail"][0].Name) + assert.Equal(t, "dicty_assay.obo", result["pass"][0].Name) + }) + + t.Run("with only failures", func(t *testing.T) { + reportDir := t.TempDir() + + failJSON := `[{"level": "ERROR", "violations": [{"missing_title": [{"subject": "test"}]}]}]` + require.NoError(t, + os.WriteFile( + filepath.Join(reportDir, "dicty_env.json"), + []byte(failJSON), + 0o600, + ), + ) + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("report-dir", reportDir, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{cli.StringFlag{Name: "report-dir"}} + + ctx := cli.NewContext(app, flagSet, nil) + result, err := ontoReport(ctx, []string{"dicty_env"}) + require.NoError(t, err) + assert.Contains(t, result, "fail") + assert.NotContains(t, result, "pass") + assert.Len(t, result["fail"], 1) + }) + + t.Run("with bad json returns error", func(t *testing.T) { + reportDir := t.TempDir() + + require.NoError(t, + os.WriteFile( + filepath.Join(reportDir, "dicty_bad.json"), + []byte("not-json"), + 0o600, + ), + ) + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("report-dir", reportDir, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{cli.StringFlag{Name: "report-dir"}} + + ctx := cli.NewContext(app, flagSet, nil) + _, err := ontoReport(ctx, []string{"dicty_bad"}) + assert.Error(t, err) + }) +} diff --git a/internal/app/deploy/share_test.go b/internal/app/deploy/share_test.go new file mode 100644 index 0000000..f0795cd --- /dev/null +++ b/internal/app/deploy/share_test.go @@ -0,0 +1,38 @@ +package deploy + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetPayload(t *testing.T) { + t.Parallel() + + pld := &Payload{ + Cluster: "erickube", + Zone: "us-central1-a", + Chart: "dicty-chart", + Path: "./helm", + Namespace: "default", + ImageTag: "abc123", + } + encoded, err := json.Marshal(pld) + require.NoError(t, err) + + wrapper, err := json.Marshal(string(encoded)) + require.NoError(t, err) + + result, err := GetPayload(wrapper) + require.NoError(t, err) + assert.Equal(t, pld, result) +} + +func TestGetPayloadInvalidJSON(t *testing.T) { + t.Parallel() + + _, err := GetPayload([]byte("not-json")) + assert.Error(t, err) +} diff --git a/internal/app/issue/issue_test.go b/internal/app/issue/issue_test.go new file mode 100644 index 0000000..ceedf07 --- /dev/null +++ b/internal/app/issue/issue_test.go @@ -0,0 +1,27 @@ +package issue + +import ( + "flag" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestIssueOpts(t *testing.T) { + t.Parallel() + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("state", "open", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "state"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + opts := issueOpts(ctx) + assert.Equal(t, "open", opts.State) + assert.Equal(t, "comments", opts.Sort) + assert.Equal(t, 30, opts.PerPage) +} diff --git a/internal/app/repository/file_io_test.go b/internal/app/repository/file_io_test.go new file mode 100644 index 0000000..609bfe3 --- /dev/null +++ b/internal/app/repository/file_io_test.go @@ -0,0 +1,57 @@ +package repository + +import ( + "flag" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestReadFiles(t *testing.T) { + t.Parallel() + + t.Run("successful read", func(t *testing.T) { + tmpDir := t.TempDir() + repoList := filepath.Join(tmpDir, "repos.txt") + inputFile := filepath.Join(tmpDir, "input.txt") + + require.NoError(t, os.WriteFile(repoList, []byte("owner/repo1"), 0o600)) + require.NoError(t, os.WriteFile(inputFile, []byte("content"), 0o600)) + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("repository-list", repoList, "") + flagSet.String("input-file", inputFile, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "repository-list"}, + cli.StringFlag{Name: "input-file"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + rpl, wnc, err := readFiles(ctx) + require.NoError(t, err) + assert.Equal(t, []byte("owner/repo1"), rpl) + assert.Equal(t, []byte("content"), wnc) + }) + + t.Run("nonexistent repo list", func(t *testing.T) { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("repository-list", "/nonexistent/repos.txt", "") + flagSet.String("input-file", "/dev/null", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "repository-list"}, + cli.StringFlag{Name: "input-file"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + _, _, err := readFiles(ctx) + assert.Error(t, err) + }) +} diff --git a/internal/app/repository/file_test.go b/internal/app/repository/file_test.go new file mode 100644 index 0000000..1ac0345 --- /dev/null +++ b/internal/app/repository/file_test.go @@ -0,0 +1,39 @@ +package repository + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseOwnerRepo(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + expected []*repo + }{ + { + name: "single repo", + input: "dictybase/foobar", + expected: []*repo{ + {owner: "dictybase", name: "foobar"}, + }, + }, + { + name: "multiple repos", + input: "dictybase/foobar\ndictybase/baz", + expected: []*repo{ + {owner: "dictybase", name: "foobar"}, + {owner: "dictybase", name: "baz"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseOwnerRepo(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/internal/app/storage/s3_test.go b/internal/app/storage/s3_test.go new file mode 100644 index 0000000..60aaff8 --- /dev/null +++ b/internal/app/storage/s3_test.go @@ -0,0 +1,45 @@ +package storage + +import ( + "flag" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestGetS3Host(t *testing.T) { + t.Parallel() + + t.Run("with port", func(t *testing.T) { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("s3-server", "minio.example.com", "") + flagSet.String("s3-server-port", "9000", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "s3-server"}, + cli.StringFlag{Name: "s3-server-port"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + host := getS3Host(ctx) + assert.Equal(t, "minio.example.com:9000", host) + }) + + t.Run("without port", func(t *testing.T) { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("s3-server", "minio.example.com", "") + flagSet.String("s3-server-port", "", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "s3-server"}, + cli.StringFlag{Name: "s3-server-port"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + host := getS3Host(ctx) + assert.Equal(t, "minio.example.com", host) + }) +} diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..a557387 --- /dev/null +++ b/internal/client/client_test.go @@ -0,0 +1,20 @@ +package client + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetLegacyGithubClient(t *testing.T) { + client, err := GetLegacyGithubClient("fake-token") + require.NoError(t, err) + assert.NotNil(t, client) +} + +func TestGetGithubClient(t *testing.T) { + client, err := GetGithubClient("fake-token") + require.NoError(t, err) + assert.NotNil(t, client) +} diff --git a/internal/cmd/cmd_test.go b/internal/cmd/cmd_test.go new file mode 100644 index 0000000..8eda3e5 --- /dev/null +++ b/internal/cmd/cmd_test.go @@ -0,0 +1,173 @@ +package cmd + +import ( + "flag" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestParseChatOpsDeploy(t *testing.T) { + t.Parallel() + + cmd := ParseChatOpsDeploy() + assert.Equal(t, "parse-chatops-deploy", cmd.Name) + assert.Equal(t, []string{"pcd"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 2) +} + +func TestOntoReportOnPullComment(t *testing.T) { + t.Parallel() + + cmd := OntoReportOnPullComment() + assert.Equal(t, "report-as-comment", cmd.Name) + assert.Equal(t, []string{"rac"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 4) +} + +func TestSetupDaggerChecksumCmd(t *testing.T) { + t.Parallel() + + cmd := SetupDaggerChecksumCmd() + assert.Equal(t, "setup-dagger-checksum", cmd.Name) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 3) +} + +func TestSetupDaggerBinCmd(t *testing.T) { + t.Parallel() + + cmd := SetupDaggerBinCmd() + assert.Equal(t, "setup-dagger-bin", cmd.Name) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 3) +} + +func TestAnalyticsReportCmd(t *testing.T) { + t.Parallel() + + cmd := AnalyticsReportCmd() + assert.Equal(t, "analytics-report", cmd.Name) + assert.Equal(t, []string{"ar"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 5) +} + +func TestDeployStatusCmd(t *testing.T) { + t.Parallel() + + cmd := DeployStatusCmd() + assert.Equal(t, "deploy-status", cmd.Name) + assert.Equal(t, []string{"ds"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 3) +} + +func TestShareDeployPayloadCmd(t *testing.T) { + t.Parallel() + + cmd := ShareDeployPayloadCmd() + assert.Equal(t, "share-deploy-payload", cmd.Name) + assert.Equal(t, []string{"sdp"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 1) +} + +func TestGenerateMkdownCmd(t *testing.T) { + t.Parallel() + + cmd := GenerateMkdownCmd() + assert.Equal(t, "doc", cmd.Name) + assert.NotNil(t, cmd.Action) + assert.Empty(t, cmd.Flags) + + app := cli.NewApp() + app.Name = "test-app" + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, flagSet, nil) + err := cmd.Action.(func(*cli.Context) error)(ctx) + assert.NoError(t, err) +} + +func TestGetK8sClusterCredentialsCmd(t *testing.T) { + t.Parallel() + + cmd := GetK8sClusterCredentialsCmd() + assert.Equal(t, "get-cluster-credentials", cmd.Name) + assert.Equal(t, []string{"gcre"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 3) +} + +func TestCommentsCountByDateCmds(t *testing.T) { + t.Parallel() + + cmd := CommentsCountByDateCmds() + assert.Equal(t, "issue-comment-count", cmd.Name) + assert.Equal(t, []string{"icc"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 1) +} + +func TestIssueCommentCmds(t *testing.T) { + t.Parallel() + + cmd := IssueCommentCmds() + assert.Equal(t, "issue-comment-report", cmd.Name) + assert.Equal(t, []string{"icr"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 2) +} + +func TestMigrateRepositories(t *testing.T) { + t.Parallel() + + cmd := MigrateRepositories() + assert.Equal(t, "migrate-repos", cmd.Name) + assert.Equal(t, []string{"mr"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 4) +} + +func TestFilesCommited(t *testing.T) { + t.Parallel() + + cmd := FilesCommited() + assert.Equal(t, "files-committed", cmd.Name) + assert.Equal(t, []string{"fc"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 5) +} + +func TestBatchMultiRepo(t *testing.T) { + t.Parallel() + + cmd := BatchMultiRepo() + assert.Equal(t, "batch-multi-repo", cmd.Name) + assert.Equal(t, []string{"bmr"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 4) +} + +func TestStoreReportCmd(t *testing.T) { + t.Parallel() + + cmd := StoreReportCmd() + assert.Equal(t, "store-report", cmd.Name) + assert.Equal(t, []string{"ur"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 7) +} + +func TestDeployChartCmd(t *testing.T) { + t.Parallel() + + cmd := DeployChartCmd() + assert.Equal(t, "deploy-chart", cmd.Name) + assert.Equal(t, []string{"dc"}, cmd.Aliases) + assert.NotNil(t, cmd.Action) + assert.Len(t, cmd.Flags, 4) +} diff --git a/internal/fake/fake_test.go b/internal/fake/fake_test.go new file mode 100644 index 0000000..7bf727a --- /dev/null +++ b/internal/fake/fake_test.go @@ -0,0 +1,65 @@ +package fake + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGithubCommitComparison(t *testing.T) { + t.Parallel() + + cc, err := GithubCommitComparison() + require.NoError(t, err) + assert.NotNil(t, cc) + assert.NotEmpty(t, cc.Files) +} + +func TestOntoReportWithEmptyError(t *testing.T) { + t.Parallel() + + path, err := OntoReportWithEmptyError() + require.NoError(t, err) + assert.Contains(t, path, "pheno_report.json") +} + +func TestOntoErrorFile(t *testing.T) { + t.Parallel() + + path, err := OntoErrorFile() + require.NoError(t, err) + assert.Contains(t, path, "report.json") +} + +func TestPullReqPayload(t *testing.T) { + t.Parallel() + + r, err := PullReqPayload("pull-request-sync.json") + require.NoError(t, err) + + data, err := io.ReadAll(r) + require.NoError(t, err) + assert.NotEmpty(t, data) +} + +func TestPushPayload(t *testing.T) { + t.Parallel() + + r, err := PushPayload() + require.NoError(t, err) + + data, err := io.ReadAll(r) + require.NoError(t, err) + assert.NotEmpty(t, data) +} + +func TestGhServerClient(t *testing.T) { + t.Parallel() + + server, _ := GhServerClient() + defer server.Close() + + assert.NotNil(t, server) +} diff --git a/internal/file/io_test.go b/internal/file/io_test.go new file mode 100644 index 0000000..a0d61d2 --- /dev/null +++ b/internal/file/io_test.go @@ -0,0 +1,85 @@ +package file + +import ( + "flag" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestInputOutput(t *testing.T) { + t.Parallel() + + t.Run("with input file and output file", func(t *testing.T) { + tmpDir := t.TempDir() + inpFile := filepath.Join(tmpDir, "input.json") + outFile := filepath.Join(tmpDir, "output.json") + + require.NoError(t, os.WriteFile(inpFile, []byte(`{"key":"value"}`), 0o600)) + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("payload-file", inpFile, "") + flagSet.String("output", outFile, "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "payload-file"}, + cli.StringFlag{Name: "output"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + inp, out, err := InputOutput(ctx) + require.NoError(t, err) + assert.NotNil(t, inp) + assert.NotNil(t, out) + assert.NotEqual(t, os.Stdout, out) + + _ = inp.Close() + _ = out.Close() + }) + + t.Run("with input file and stdout", func(t *testing.T) { + tmpDir := t.TempDir() + inpFile := filepath.Join(tmpDir, "input.json") + + require.NoError(t, os.WriteFile(inpFile, []byte(`{"key":"value"}`), 0o600)) + + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("payload-file", inpFile, "") + flagSet.String("output", "", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "payload-file"}, + cli.StringFlag{Name: "output"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + inp, out, err := InputOutput(ctx) + require.NoError(t, err) + assert.NotNil(t, inp) + assert.Equal(t, os.Stdout, out) + + _ = inp.Close() + }) + + t.Run("nonexistent input file", func(t *testing.T) { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + flagSet.String("payload-file", "/nonexistent/file.json", "") + flagSet.String("output", "", "") + + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "payload-file"}, + cli.StringFlag{Name: "output"}, + } + + ctx := cli.NewContext(app, flagSet, nil) + _, _, err := InputOutput(ctx) + assert.Error(t, err) + }) +} diff --git a/internal/github/builder_test.go b/internal/github/builder_test.go index ec7ac22..284a64e 100644 --- a/internal/github/builder_test.go +++ b/internal/github/builder_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/dictyBase-docker/github-actions/internal/fake" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -107,3 +108,29 @@ func TestFilterChain(t *testing.T) { "should have GWDI_Strain_Annotation.txt file", ) } + +func TestFilterSuffixEmpty(t *testing.T) { + t.Parallel() + + b := &ChangedFilesBuilder{files: nil} + result := b.FilterSuffix("obo") + assert.Equal(t, b, result) +} + +func TestFilterDeletedEmpty(t *testing.T) { + t.Parallel() + + b := &ChangedFilesBuilder{files: nil} + result := b.FilterDeleted(true) + assert.Equal(t, b, result) +} + +func TestFilterUniqueByNameSingleFile(t *testing.T) { + t.Parallel() + + b := &ChangedFilesBuilder{ + files: []*ChangedFiles{{Name: "foo.obo", Change: "modified"}}, + } + result := b.FilterUniqueByName() + assert.Equal(t, b, result) +} diff --git a/internal/github/manager_test.go b/internal/github/manager_test.go index 8b74d57..d77db63 100644 --- a/internal/github/manager_test.go +++ b/internal/github/manager_test.go @@ -1,10 +1,12 @@ package github import ( + "strings" "testing" "github.com/dictyBase-docker/github-actions/internal/fake" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -91,3 +93,61 @@ func testPull(t *testing.T, name string) { ) testCommitList(t, b) } + +func TestFilterCommittedFilesPush(t *testing.T) { + t.Parallel() + assert := require.New(t) + r, err := fake.PushPayload() + assert.NoError(err, "should not receive any error from reading push payload") + + server, client := fake.GhServerClient() + defer server.Close() + + files, err := FilterCommittedFiles(&CommittedFilesParams{ + Client: client, + Input: r, + Event: "push", + FileSuffix: "obo", + SkipDeleted: false, + }) + assert.NoError(err, "should not receive any error from filtering committed files") + assert.Len(files, 3, "should have 3 obo files") +} + +func TestFilterCommittedFilesPullRequest(t *testing.T) { + t.Parallel() + assert := require.New(t) + r, err := fake.PullReqPayload("pull-request-sync.json") + assert.NoError(err, "should not receive any error from reading pull request payload") + + server, client := fake.GhServerClient() + defer server.Close() + + files, err := FilterCommittedFiles(&CommittedFilesParams{ + Client: client, + Input: r, + Event: "pull-request", + FileSuffix: "", + SkipDeleted: true, + }) + assert.NoError(err, "should not receive any error from filtering committed files") + assert.Len(files, 11, "should have 11 files after removing deleted ones") +} + +func TestFilterCommittedFilesUnsupportedEvent(t *testing.T) { + t.Parallel() + + server, client := fake.GhServerClient() + defer server.Close() + + r := strings.NewReader(`{}`) + _, err := FilterCommittedFiles(&CommittedFilesParams{ + Client: client, + Input: r, + Event: "unknown-event", + FileSuffix: "", + SkipDeleted: false, + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "not supported") +} diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go new file mode 100644 index 0000000..eac44cd --- /dev/null +++ b/internal/logger/logger_test.go @@ -0,0 +1,96 @@ +package logger + +import ( + "flag" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func newLoggerCtx(logFormat, logLevel string) *cli.Context { + flagSet := flag.NewFlagSet("test", flag.ContinueOnError) + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "log-format", Value: "json"}, + cli.StringFlag{Name: "log-level", Value: "info"}, + } + + flagSet.String("log-format", logFormat, "") + flagSet.String("log-level", logLevel, "") + _ = flagSet.Parse([]string{}) + + return cli.NewContext(app, flagSet, nil) +} + +func TestGetLogger(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + logFormat string + logLevel string + wantFormatter any + wantLevel logrus.Level + }{ + { + name: "default", + logFormat: "json", + logLevel: "info", + wantFormatter: &logrus.JSONFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.InfoLevel, + }, + { + name: "text formatter", + logFormat: "text", + logLevel: "info", + wantFormatter: &logrus.TextFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.InfoLevel, + }, + { + name: "debug level", + logFormat: "json", + logLevel: "debug", + wantFormatter: &logrus.JSONFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.DebugLevel, + }, + { + name: "warn level", + logFormat: "json", + logLevel: "warn", + wantFormatter: &logrus.JSONFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.WarnLevel, + }, + { + name: "error level", + logFormat: "json", + logLevel: "error", + wantFormatter: &logrus.JSONFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.ErrorLevel, + }, + { + name: "fatal level", + logFormat: "json", + logLevel: "fatal", + wantFormatter: &logrus.JSONFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.FatalLevel, + }, + { + name: "panic level", + logFormat: "json", + logLevel: "panic", + wantFormatter: &logrus.JSONFormatter{TimestampFormat: "02/Jan/2006:15:04:05"}, + wantLevel: logrus.PanicLevel, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := newLoggerCtx(tt.logFormat, tt.logLevel) + entry := GetLogger(ctx) + assert.NotNil(t, entry) + assert.Equal(t, tt.wantLevel, entry.Logger.Level) + assert.IsType(t, tt.wantFormatter, entry.Logger.Formatter) + }) + } +} diff --git a/internal/ontology/error_test.go b/internal/ontology/error_test.go new file mode 100644 index 0000000..09b6495 --- /dev/null +++ b/internal/ontology/error_test.go @@ -0,0 +1,46 @@ +package ontology + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestViolationNotFoundError_Error(t *testing.T) { + t.Parallel() + + err := &ViolationNotFoundError{Level: "ERROR"} + assert.Equal(t, "violation ERROR is not found", err.Error()) +} + +func TestIsViolationNotFound(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + expected bool + }{ + { + name: "violation not found error", + err: &ViolationNotFoundError{Level: "WARN"}, + expected: true, + }, + { + name: "generic error", + err: errors.New("something else"), + expected: false, + }, + { + name: "nil error", + err: nil, + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, IsViolationNotFound(tt.err)) + }) + } +} diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go new file mode 100644 index 0000000..587e4da --- /dev/null +++ b/internal/runner/runner_test.go @@ -0,0 +1,50 @@ +package runner + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewGcloudWithPath(t *testing.T) { + t.Parallel() + + t.Run("existing file", func(t *testing.T) { + tmpDir := t.TempDir() + path := filepath.Join(tmpDir, "gcloud") + require.NoError(t, os.WriteFile(path, []byte("fake"), 0o600)) + + gcloud, err := NewGcloudWithPath(path) + require.NoError(t, err) + assert.Equal(t, path, gcloud.Cmd) + }) + + t.Run("nonexistent file", func(t *testing.T) { + _, err := NewGcloudWithPath("/nonexistent/gcloud") + require.Error(t, err) + assert.Contains(t, err.Error(), "does not exist") + }) +} + +func TestNewHelmWithPath(t *testing.T) { + t.Parallel() + + t.Run("existing file", func(t *testing.T) { + tmpDir := t.TempDir() + path := filepath.Join(tmpDir, "helm") + require.NoError(t, os.WriteFile(path, []byte("fake"), 0o600)) + + helm, err := NewHelmWithPath(path) + require.NoError(t, err) + assert.Equal(t, path, helm.Cmd) + }) + + t.Run("nonexistent file", func(t *testing.T) { + _, err := NewHelmWithPath("/nonexistent/helm") + require.Error(t, err) + assert.Contains(t, err.Error(), "does not exist") + }) +}