From 23319760703cd61229af2e154fe7d21347dd54da Mon Sep 17 00:00:00 2001 From: Andreas Salhus Bakseter <141913422+baksetercx@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:23:21 +0100 Subject: [PATCH] Support setting Python version when generating Dockerfile --- main.go | 5 +- pkg/build/Dockerfile.python.tmpl | 4 +- pkg/build/_test/Dockerfile.python.test1 | 39 ++++++++ ...le.python.test => Dockerfile.python.test2} | 0 pkg/build/_test/Dockerfile.python.test3 | 39 ++++++++ pkg/build/build.go | 8 +- pkg/build/generate.go | 16 +-- pkg/build/generate_python.go | 98 ++++++++++++++++++- pkg/build/generate_test.go | 53 +++++++++- pkg/create/create.go | 15 +-- pkg/deploy/grafana.go | 11 +-- pkg/githubactions/githubactions.go | 23 +---- pkg/scan/scan.go | 35 ++----- pkg/shared/after.go | 6 +- pkg/style/style.go | 16 +++ pkg/upgrade/upgrade.go | 19 ++-- pkg/utils/utils.go | 9 +- pkg/utils/utils_test.go | 22 +++++ 18 files changed, 309 insertions(+), 109 deletions(-) create mode 100644 pkg/build/_test/Dockerfile.python.test1 rename pkg/build/_test/{Dockerfile.python.test => Dockerfile.python.test2} (100%) create mode 100644 pkg/build/_test/Dockerfile.python.test3 create mode 100644 pkg/utils/utils_test.go diff --git a/main.go b/main.go index 1640809..8fa8ef6 100644 --- a/main.go +++ b/main.go @@ -65,9 +65,6 @@ func main() { ctx := context.Background() if err := app.Run(ctx, os.Args); err != nil { - style.Print( - fmt.Sprintf("\n\nERROR: %s", err), - &style.PrintOptions{Color: "red"}, - ) + style.PrintError(fmt.Sprintf("\n\nERROR: %s", err)) } } diff --git a/pkg/build/Dockerfile.python.tmpl b/pkg/build/Dockerfile.python.tmpl index 7e27414..690de44 100644 --- a/pkg/build/Dockerfile.python.tmpl +++ b/pkg/build/Dockerfile.python.tmpl @@ -1,4 +1,4 @@ -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS build +FROM ghcr.io/astral-sh/uv:python{{ .PythonVersion }}-bookworm-slim AS build LABEL maintainer="elvia@elvia.no" ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy @@ -16,7 +16,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \ uv sync --frozen --no-dev -FROM python:3.12-slim-bookworm +FROM python:{{ .PythonVersion }}-slim-bookworm LABEL maintainer="elvia@elvia.no" RUN addgroup application-group --gid 1001 && \ diff --git a/pkg/build/_test/Dockerfile.python.test1 b/pkg/build/_test/Dockerfile.python.test1 new file mode 100644 index 0000000..ce9bdb4 --- /dev/null +++ b/pkg/build/_test/Dockerfile.python.test1 @@ -0,0 +1,39 @@ +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS build +LABEL maintainer="elvia@elvia.no" + +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy + +WORKDIR /app + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project --no-dev + +ADD . /app + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + + +FROM python:3.13-slim-bookworm +LABEL maintainer="elvia@elvia.no" + +RUN addgroup application-group --gid 1001 && \ + adduser application-user --uid 1001 \ + --ingroup application-group \ + --disabled-password + +WORKDIR /app + +COPY --from=build /app . + +ENV PATH="/app/.venv/bin:$PATH" + +RUN chown --recursive application-user . +USER application-user + +EXPOSE 8080 + +CMD ["fastapi", "run", "--host", "0.0.0.0", "--port", "8080", "/app/app/main.py"] + diff --git a/pkg/build/_test/Dockerfile.python.test b/pkg/build/_test/Dockerfile.python.test2 similarity index 100% rename from pkg/build/_test/Dockerfile.python.test rename to pkg/build/_test/Dockerfile.python.test2 diff --git a/pkg/build/_test/Dockerfile.python.test3 b/pkg/build/_test/Dockerfile.python.test3 new file mode 100644 index 0000000..e6970a0 --- /dev/null +++ b/pkg/build/_test/Dockerfile.python.test3 @@ -0,0 +1,39 @@ +FROM ghcr.io/astral-sh/uv:python3.10-bookworm-slim AS build +LABEL maintainer="elvia@elvia.no" + +ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy + +WORKDIR /app + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project --no-dev + +ADD . /app + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev + + +FROM python:3.10-slim-bookworm +LABEL maintainer="elvia@elvia.no" + +RUN addgroup application-group --gid 1001 && \ + adduser application-user --uid 1001 \ + --ingroup application-group \ + --disabled-password + +WORKDIR /app + +COPY --from=build /app . + +ENV PATH="/app/.venv/bin:$PATH" + +RUN chown --recursive application-user . +USER application-user + +EXPOSE 8080 + +CMD ["fastapi", "run", "--host", "0.0.0.0", "--port", "8080", "/app/app/main.py"] + diff --git a/pkg/build/build.go b/pkg/build/build.go index 20f6268..ada0742 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -129,9 +129,8 @@ func Build(_ context.Context, c *cli.Command) error { possibleSystemName := c.String("system-name") if possibleSystemName == "" { - style.Print( + style.PrintInfo( "System name not provided, will try to use the current git repository name.", - nil, ) repositoryName, err := utils.ResolveRepositoryName("") @@ -163,9 +162,8 @@ func Build(_ context.Context, c *cli.Command) error { } if c.Bool("generate-only") { - style.Print( + style.PrintSuccess( fmt.Sprintf("Dockerfile generated at %s\n", dockerfilePath), - nil, ) return nil @@ -178,7 +176,7 @@ func Build(_ context.Context, c *cli.Command) error { skipAuthentication := c.Bool("skip-authentication") || !push if strings.Contains(registry, "azurecr.io") && !skipAuthentication { - style.Print("Azure registry detected, will try to authenticate with Azure.", nil) + style.PrintInfo("Azure registry detected, will try to authenticate with Azure.") azureTenantID := utils.StringWithDefault( c.String("azure-tenant-id"), diff --git a/pkg/build/generate.go b/pkg/build/generate.go index 9db6848..a6125cd 100644 --- a/pkg/build/generate.go +++ b/pkg/build/generate.go @@ -49,14 +49,6 @@ func generateDockerfile( } return dockerfile, buildContext, nil - } else if strings.HasPrefix(projectFile, "Dockerfile") || - strings.HasSuffix(projectFile, "Dockerfile") || - strings.Contains(projectFile, "Dockerfile") { - if options.BuildContext == "" { - return projectFile, path.Dir(projectFile), nil - } - - return projectFile, options.BuildContext, nil } else if strings.HasSuffix(projectFile, "uv.lock") { dockerfile, buildContext, err := generateDockerfileForPython( projectFile, @@ -68,6 +60,14 @@ func generateDockerfile( } return dockerfile, buildContext, nil + } else if strings.HasPrefix(projectFile, "Dockerfile") || + strings.HasSuffix(projectFile, "Dockerfile") || + strings.Contains(projectFile, "Dockerfile") { + if options.BuildContext == "" { + return projectFile, path.Dir(projectFile), nil + } + + return projectFile, options.BuildContext, nil } return "", "", fmt.Errorf( diff --git a/pkg/build/generate_python.go b/pkg/build/generate_python.go index 7125172..d10f6dc 100644 --- a/pkg/build/generate_python.go +++ b/pkg/build/generate_python.go @@ -1,10 +1,20 @@ package build import ( + "fmt" + "io" + "os" + "path" + "slices" + "strings" + + "github.com/3lvia/cli/pkg/style" "github.com/3lvia/cli/pkg/utils" ) -type DockerfileVariablesPython struct{} +type DockerfileVariablesPython struct { + PythonVersion string +} func generateDockerfileForPython( projectFile string, @@ -16,7 +26,14 @@ func generateDockerfileForPython( options.BuildContext, ) - dockerfileVariables := DockerfileVariablesPython{} + pythonVersion := getPythonVersion( + path.Dir(projectFile), + buildContext, + ) + + dockerfileVariables := DockerfileVariablesPython{ + PythonVersion: pythonVersion, + } const templateFile = "Dockerfile.python.tmpl" @@ -33,3 +50,80 @@ func generateDockerfileForPython( return dockerfilePath, buildContext, nil } + +func getPythonVersion(directories ...string) string { + const defaultPythonVersion = "3.13" + + // removes duplicates + slices.Sort(directories) + directories = slices.Compact(directories) + + style.PrintInfo( + fmt.Sprintf( + "Looking for .python-version file in directories: '%v'.", + strings.Join(directories, ", "), + ), + ) + + for _, directory := range directories { + versionFile := path.Join(directory, ".python-version") + + if _, err := os.Stat(versionFile); os.IsNotExist(err) { + style.PrintWarning( + fmt.Sprintf( + "No .python-version file found in '%s', will try next directory.\n", + directory, + ), + ) + + continue + } + + file, err := os.Open(versionFile) + if err != nil { + style.PrintWarning( + fmt.Sprintf( + "Failed to open .python-version file in '%s', will try next directory.\n", + directory, + ), + ) + + continue + } + + defer file.Close() + + contents, err := io.ReadAll(file) + if err != nil { + style.PrintWarning( + fmt.Sprintf( + "Failed to read .python-version file in '%s', will try next directory.\n", + directory, + ), + ) + + continue + } + + pythonVersion := strings.TrimSpace(string(contents)) + + style.PrintInfo( + fmt.Sprintf( + "Found .python-version file in '%s' with version %s.\n", + directory, + pythonVersion, + ), + ) + + return pythonVersion + } + + style.PrintWarning( + fmt.Sprintf( + "Did not find any .python-version files, using default version %s.", + defaultPythonVersion, + ), + ) + + return defaultPythonVersion +} diff --git a/pkg/build/generate_test.go b/pkg/build/generate_test.go index 0367154..b5617b5 100644 --- a/pkg/build/generate_test.go +++ b/pkg/build/generate_test.go @@ -3,6 +3,7 @@ package build import ( "os" "path/filepath" + "strconv" "testing" ) @@ -296,10 +297,10 @@ func TestGenerateGoDockerfile(t *testing.T) { } } -func TestGeneratePythonDockerfile(t *testing.T) { +func TestGeneratePythonDockerfile1(t *testing.T) { t.Parallel() - expectedDockerfile, err := os.ReadFile("_test/Dockerfile.python.test") + expectedDockerfile, err := os.ReadFile("_test/Dockerfile.python.test1") if err != nil { t.Errorf("Error reading file: %v", err) } @@ -334,6 +335,54 @@ func TestGeneratePythonDockerfile(t *testing.T) { } } +func TestGeneratePythonDockerfile2(t *testing.T) { + t.Parallel() + + for i, version := range []string{"3.12", "3.10"} { + expectedDockerfile, err := os.ReadFile("_test/Dockerfile.python.test" + strconv.FormatInt(int64(i+2), 10)) + if err != nil { + t.Errorf("Error reading file: %v", err) + } + + tempDir := t.TempDir() + projectFile := filepath.Join(tempDir, "uv.lock") + expectedBuildContext := tempDir + + const applicationName = "demo-api-python" + + file, err := os.Create(filepath.Join(tempDir, ".python-version")) + if err != nil { + t.Errorf("Error creating file: %v", err) + } + + if _, err := file.WriteString(version + "\n"); err != nil { + t.Errorf("Error writing to file: %v", err) + } + + actualDockerfilePath, actualBuildContext, err := generateDockerfile( + projectFile, + applicationName, + GenerateDockerfileOptions{}, + ) + if err != nil { + t.Errorf("Error generating Dockerfile: %v", err) + } + + actualDockerfile, err := os.ReadFile(actualDockerfilePath) + if err != nil { + t.Errorf("Error reading file: %v", err) + } + + if string(expectedDockerfile) != string(actualDockerfile) { + t.Errorf("Dockerfile mismatch: expected %s, got %s", expectedDockerfile, actualDockerfile) + } + + if expectedBuildContext != actualBuildContext { + t.Errorf("Build context mismatch: expected %s, got %s", expectedBuildContext, actualBuildContext) + } + } +} + func TestGenerateDockerfileWithDockerfile1(t *testing.T) { t.Parallel() diff --git a/pkg/create/create.go b/pkg/create/create.go index 9ac34a0..d9f6dfe 100644 --- a/pkg/create/create.go +++ b/pkg/create/create.go @@ -132,20 +132,14 @@ func Create(ctx context.Context, c *cli.Command) error { log.Fatal("pipx, which is required for installing cookiecutter, is not installed. Please install it first.") } - style.Print( - "Installing cookiecutter...", - nil, - ) + style.PrintInfo("Installing cookiecutter...") installCookiecutterOutput := installCookiecutterCommand(nil) if command.IsError(installCookiecutterOutput) { return cli.Exit("Failed to install cookiecutter.", 1) } - style.Print( - "Cookiecutter installed!", - &style.PrintOptions{Color: "green"}, - ) + style.PrintSuccess("Cookiecutter installed!") } else { return cli.Exit("Cookiecutter is required for creating a new project. Please install it first.", 1) } @@ -200,10 +194,7 @@ func Create(ctx context.Context, c *cli.Command) error { return cli.Exit(err, 1) } - style.Print( - fmt.Sprintf("Successfully created project at '%s'!", projectDirectory), - &style.PrintOptions{Color: "green"}, - ) + style.PrintSuccess(fmt.Sprintf("Successfully created project at '%s'!", projectDirectory)) return nil } diff --git a/pkg/deploy/grafana.go b/pkg/deploy/grafana.go index 83d83f0..8b2ce73 100644 --- a/pkg/deploy/grafana.go +++ b/pkg/deploy/grafana.go @@ -134,9 +134,8 @@ func addGrafanaDeploymentAnnotation( }, } - style.Print( + style.PrintInfo( fmt.Sprintf("Sending deploy annotation to Grafana: %v\n", grafanaAnnotation), - nil, ) body, err := json.Marshal(grafanaAnnotation) @@ -154,9 +153,8 @@ func addGrafanaDeploymentAnnotation( RetryAttempts, RetryDelay, func(i int, _ time.Duration) error { - style.Print( + style.PrintInfo( fmt.Sprintf("Sending deploy annotation to Grafana, attempt %d\n\n", i), - nil, ) statusCode, err := sendRequest( @@ -177,15 +175,14 @@ func addGrafanaDeploymentAnnotation( }, ) if err != nil { - style.Print( + style.PrintError( fmt.Sprintf("Failed to send deploy annotation to Grafana after %d attempts\n", RetryAttempts), - &style.PrintOptions{Color: "red"}, ) return err } - style.Print("Deploy annotation sent to Grafana!\n", &style.PrintOptions{Color: "green"}) + style.PrintSuccess("Deploy annotation sent to Grafana!\n") return nil } diff --git a/pkg/githubactions/githubactions.go b/pkg/githubactions/githubactions.go index 203ea49..3cfa6c7 100644 --- a/pkg/githubactions/githubactions.go +++ b/pkg/githubactions/githubactions.go @@ -80,10 +80,7 @@ func GitHubActions(ctx context.Context, c *cli.Command) error { return cli.Exit(err, 1) } - style.Print( - "Successfully added GitHub Actions to the project!\n", - &style.PrintOptions{Color: "green"}, - ) + style.PrintSuccess("Successfully added GitHub Actions to the project!\n") return nil } @@ -103,10 +100,7 @@ func CreateDeployWorkflow( fullGithubActionsDir := path.Join(outputDirectory, githubActionsDir) if _, err := os.Stat(fullGithubActionsDir); os.IsNotExist(err) { - style.Print( - fmt.Sprintf("Creating directory '%s'.\n", githubActionsDir), - nil, - ) + style.PrintInfo(fmt.Sprintf("Creating directory '%s'.\n", githubActionsDir)) if err := os.MkdirAll(fullGithubActionsDir, 0o755); err != nil { return fmt.Errorf("Failed to create directory '%s'", fullGithubActionsDir) @@ -143,12 +137,11 @@ func CreateDeployWorkflow( return err } - style.Print( + style.PrintWarning( fmt.Sprintf( "Replacing placeholders in workflow file '%s'.\nYou may need to manually fill in some values yourself.\n", workflowFilePath, ), - &style.PrintOptions{Color: "yellow"}, ) replaceWorkflowPlaceholdersOptions := &ReplaceWorkflowPlaceholdersOptions{ @@ -174,10 +167,7 @@ func CreateDeployWorkflow( return "NOTE: if you have not done so already, you will need to add your system/repository to" + " https://github.com/3lvia/github-repositories-terraform to enable deployments from GitHub Actions." }() - style.Print( - terraformReminder+"\n", - &style.PrintOptions{Color: "yellow"}, - ) + style.PrintWarning(terraformReminder + "\n") return nil } @@ -456,10 +446,7 @@ func resolveHelmValuesFile( return "", err } - style.Print( - fmt.Sprintf("Created Helm values file at '%s'.\n", newHelmValuesFile), - nil, - ) + style.PrintInfo(fmt.Sprintf("Created Helm values file at '%s'.\n", newHelmValuesFile)) return defaultHelmValuesFile, nil } diff --git a/pkg/scan/scan.go b/pkg/scan/scan.go index 1ae82ac..6519e63 100644 --- a/pkg/scan/scan.go +++ b/pkg/scan/scan.go @@ -36,10 +36,7 @@ func Scan(_ context.Context, c *cli.Command) error { // Required args imageName := c.Args().First() if imageName == "" { - style.Print( - "Image name not provided.", - &style.PrintOptions{Color: "red"}, - ) + style.PrintError("Image name not provided.") cli.ShowSubcommandHelpAndExit(c, 1) } @@ -148,10 +145,7 @@ func ScanImage( if _, err := os.Stat("trivy.json"); errors.Is(err, os.ErrNotExist) { if disableError { - style.Print( - "Trivy did not produce any output.", - &style.PrintOptions{Color: "yellow"}, - ) + style.PrintWarning("Trivy did not produce any output.") return nil } @@ -160,10 +154,7 @@ func ScanImage( } if slices.Contains(formats, "table") { - style.Print( - "Converted results to table format.", - nil, - ) + style.PrintInfo("Converted results to table format.") convertOutput := convertCommand( "table", @@ -175,10 +166,7 @@ func ScanImage( } if slices.Contains(formats, "sarif") { - style.Print( - "Converted results to SARIF format.", - nil, - ) + style.PrintInfo("Converted results to SARIF format.") convertOutput := convertCommand( "sarif", @@ -190,10 +178,7 @@ func ScanImage( } if slices.Contains(formats, "markdown") { - style.Print( - "Converting results to markdown format.", - nil, - ) + style.PrintInfo("Converting results to markdown format.") result, err := parseJSONOutput() if err != nil { @@ -206,10 +191,7 @@ func ScanImage( } if len(markdown) == 0 { - style.Print( - "Markdown output is empty, will write to empty file", - &style.PrintOptions{Color: "yellow"}, - ) + style.PrintWarning("Markdown output is empty, will write to empty file") } err = os.WriteFile("trivy.md", markdown, 0o644) @@ -224,10 +206,7 @@ func ScanImage( return err } } else { - style.Print( - "Keeping pre-existing JSON output.", - nil, - ) + style.PrintInfo("Keeping pre-existing JSON output.") } if command.IsError(scanImageOutput) { diff --git a/pkg/shared/after.go b/pkg/shared/after.go index 64762e2..1160ef4 100644 --- a/pkg/shared/after.go +++ b/pkg/shared/after.go @@ -14,14 +14,12 @@ import ( func checkVersionAfter(ctx context.Context, version string) error { latestVersion, _ := upgrade.GetLatestCLIVersion(ctx) if semver.Compare("v"+version, "v"+latestVersion) == -1 { - style.Print( + style.PrintWarning( fmt.Sprintf("\n\nA new version of 3lv is available! %s -> %s", version, latestVersion), - &style.PrintOptions{Color: "yellow"}, ) - style.Print( + style.PrintWarning( "Run `3lv upgrade` to update to the latest version.", - &style.PrintOptions{Color: "yellow"}, ) } diff --git a/pkg/style/style.go b/pkg/style/style.go index b5c1016..7b831b2 100644 --- a/pkg/style/style.go +++ b/pkg/style/style.go @@ -21,6 +21,22 @@ func Print(message string, options *PrintOptions) { fmt.Println(style.Render(message)) } +func PrintInfo(message string) { + Print(message, &PrintOptions{Color: "blue"}) +} + +func PrintSuccess(message string) { + Print(message, &PrintOptions{Color: "green"}) +} + +func PrintWarning(message string) { + Print(message, &PrintOptions{Color: "yellow"}) +} + +func PrintError(message string) { + Print(message, &PrintOptions{Color: "red"}) +} + func getColorCode(color string) string { switch color { case "red": diff --git a/pkg/upgrade/upgrade.go b/pkg/upgrade/upgrade.go index 4857d48..82219e1 100644 --- a/pkg/upgrade/upgrade.go +++ b/pkg/upgrade/upgrade.go @@ -68,9 +68,8 @@ func Upgrade(ctx context.Context, c *cli.Command, version string) error { if c.Bool("use-existing-binary-location") { currentBinary, err := os.Executable() if err != nil { - style.Print( - "Could not determine the current binary location, will install to "+defaultInstallLocation, - &style.PrintOptions{Color: "red"}, + style.PrintError( + "Could not determine the current binary location, will install to " + defaultInstallLocation, ) return defaultInstallLocation @@ -88,15 +87,13 @@ func Upgrade(ctx context.Context, c *cli.Command, version string) error { } if semver.Compare("v"+version, "v"+latestVersion) != -1 { - style.Print( - "You are already using the latest version of 3lv: "+version, - &style.PrintOptions{Color: "green"}, + style.PrintSuccess( + "You are already using the latest version of 3lv: " + version, ) if !c.Bool("force-reinstall") { - style.Print( + style.PrintInfo( "Use the --force-reinstall flag to force the reinstallation of the 3lv binary.", - &style.PrintOptions{Color: "yellow"}, ) return cli.Exit("", 0) @@ -108,9 +105,8 @@ func Upgrade(ctx context.Context, c *cli.Command, version string) error { return cli.Exit(err, 1) } - style.Print( + style.PrintInfo( fmt.Sprintf("Upgrading 3lv from %s to %s...", version, latestVersion), - &style.PrintOptions{Color: "yellow"}, ) tempDir, err := os.MkdirTemp("", "3lv-upgrade") @@ -160,9 +156,8 @@ func Upgrade(ctx context.Context, c *cli.Command, version string) error { return cli.Exit(installCommandOutput.Error, 1) } - style.Print( + style.PrintSuccess( fmt.Sprintf("Successfully upgraded 3lv to %s!", latestVersion), - &style.PrintOptions{Color: "green"}, ) return nil diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 687bd88..e835e2a 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -89,13 +89,13 @@ func StringWithDefault(value, defaultValue string) string { } func WriteFileWithTemplate( - dir string, + directory string, fileName string, templateFile string, templates embed.FS, variables any, ) (string, error) { - filePath := path.Join(dir, fileName) + filePath := path.Join(directory, fileName) file, err := os.Create(filePath) if err != nil { @@ -125,9 +125,8 @@ func WriteFileWithTemplate( // Will only return false if the response is "n". func PromptYesNo(question string, nonInteractive bool) (bool, error) { - style.Print( - question+" (y/n): ", - nil, + style.PrintInfo( + question + " (y/n): ", ) if nonInteractive { diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000..9e0fd78 --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,22 @@ +package utils + +import "testing" + +func TestRemoveZeroValues(t *testing.T) { + t.Parallel() + + slice := []string{"", "a", "", "b", "c", "", "d", ""} + expected := []string{"a", "b", "c", "d"} + + result := RemoveZeroValues(slice) + + if len(result) != len(expected) { + t.Fatalf("Expected %v, but got %v", expected, result) + } + + for i, value := range expected { + if result[i] != value { + t.Fatalf("Expected %v, but got %v", expected, result) + } + } +}