Files
Tosone c9ac979c95 refactor: migrate json encoding to json/v2 package across codebase
Replace all usages of standard encoding/json with encoding/json/v2 and jsontext
where applicable, update decoding logic to use json.UnmarshalRead and
json.MarshalWrite instead of manual encoder/decoder instances.

Verification:
go test -race ./pkg/... ./cmd/...
2026-08-27 14:28:01 +08:00

272 lines
8.8 KiB
Go

// Copyright 2026 sigma
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"encoding/json/v2"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"github.com/go-sigma/sigma/pkg/api/enums"
)
func TestNewRootCmd(t *testing.T) {
t.Setenv("SIGMA_SERVER", "http://example.test")
command := newRootCmd()
require.Equal(t, "sigma-cli", command.Use)
require.NotNil(t, findCommand(t, command, "login"))
require.NotNil(t, findCommand(t, command, "namespace"))
require.NotNil(t, findCommand(t, command, "repo"))
require.NotNil(t, findCommand(t, command, "tag"))
require.NotNil(t, findCommand(t, command, "artifact"))
require.NotNil(t, findCommand(t, command, "member"))
require.NotNil(t, findCommand(t, command, "user"))
}
func TestOptionsInitialize(t *testing.T) {
credentialsFile := filepath.Join(t.TempDir(), "credentials.json")
require.NoError(t, saveCredentials(credentialsFile, credentials{
Server: "http://saved.example.test/",
Username: "sigma",
Password: "secret",
}))
opts := &options{
server: "http://flag.example.test/",
credentialsFile: credentialsFile,
timeout: time.Second,
}
command := &cobra.Command{Use: "test"}
command.PersistentFlags().String("server", opts.server, "")
require.NoError(t, opts.initialize(context.Background(), command))
require.Equal(t, "http://saved.example.test", opts.client.baseURL)
require.Equal(t, "sigma", opts.client.username)
require.Equal(t, "secret", opts.client.password)
require.NoError(t, command.PersistentFlags().Set("server", "http://flag.example.test/"))
require.NoError(t, opts.initialize(context.Background(), command))
require.Equal(t, "http://flag.example.test", opts.client.baseURL)
}
func TestOptionsInitializeRequiresCredentials(t *testing.T) {
opts := &options{
server: "http://example.test",
credentialsFile: filepath.Join(t.TempDir(), "missing.json"),
timeout: time.Second,
}
command := &cobra.Command{Use: "test"}
command.PersistentFlags().String("server", opts.server, "")
err := opts.initialize(context.Background(), command)
require.ErrorContains(t, err, "basic auth credentials are required")
}
func TestNewLoginCmd(t *testing.T) {
credentialsFile := filepath.Join(t.TempDir(), "credentials.json")
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/api/v1/users/login", r.URL.Path)
username, password, ok := r.BasicAuth()
require.True(t, ok)
require.Equal(t, "sigma", username)
require.Equal(t, "secret", password)
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(server.Close)
opts := &options{
server: server.URL + "/",
credentialsFile: credentialsFile,
timeout: time.Second,
}
command := newLoginCmd(opts)
output := executeCommand(t, command, "-u", "sigma", "-p", "secret")
require.Contains(t, output, `"status": "logged_in"`)
cred, err := loadCredentials(credentialsFile)
require.NoError(t, err)
require.Equal(t, server.URL, cred.Server)
require.Equal(t, "sigma", cred.Username)
require.Equal(t, "secret", cred.Password)
}
func TestClientDo(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/api/v1/test", r.URL.Path)
require.Equal(t, "value", r.URL.Query().Get("name"))
username, password, ok := r.BasicAuth()
require.True(t, ok)
require.Equal(t, "override", username)
require.Equal(t, "secret", password)
var body map[string]string
require.NoError(t, json.UnmarshalRead(r.Body, &body))
require.Equal(t, "payload", body["name"])
w.Header().Set("Content-Type", "application/json")
_, err := w.Write([]byte(`{"status":"ok"}`))
require.NoError(t, err)
}))
t.Cleanup(server.Close)
cli := newClient(server.URL, "default", "default-secret", time.Second, false)
var out map[string]string
query := url.Values{"name": []string{"value"}}
err := cli.do(t.Context(), http.MethodPost, "/api/v1/test", query, map[string]string{"name": "payload"}, &out, basicAuth{
username: "override",
password: "secret",
})
require.NoError(t, err)
require.Equal(t, "ok", out["status"])
}
func TestClientDoReturnsFormattedError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte(`{"message":"bad request"}`))
require.NoError(t, err)
}))
t.Cleanup(server.Close)
err := newClient(server.URL, "", "", time.Second, false).
do(t.Context(), http.MethodGet, "/api/v1/fail", nil, nil, nil, basicAuth{})
require.ErrorContains(t, err, "GET /api/v1/fail")
require.ErrorContains(t, err, `"message": "bad request"`)
}
func TestCredentialsFileLifecycle(t *testing.T) {
missing := filepath.Join(t.TempDir(), "missing.json")
cred, err := loadCredentials(missing)
require.NoError(t, err)
require.Empty(t, cred)
path := filepath.Join(t.TempDir(), "nested", "credentials.json")
expected := credentials{
Server: "http://example.test",
Username: "sigma",
Password: "secret",
}
require.NoError(t, saveCredentials(path, expected))
info, err := os.Stat(path)
require.NoError(t, err)
require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
actual, err := loadCredentials(path)
require.NoError(t, err)
require.Equal(t, expected, actual)
invalid := filepath.Join(t.TempDir(), "invalid.json")
require.NoError(t, os.WriteFile(invalid, []byte("{"), 0o600))
_, err = loadCredentials(invalid)
require.ErrorContains(t, err, "decode credentials file")
}
func TestEnvAndCredentialsFileDefaults(t *testing.T) {
t.Setenv("SIGMA_TEST_ENV", " configured ")
require.Equal(t, "configured", envOrDefault("SIGMA_TEST_ENV", "fallback"))
t.Setenv("SIGMA_TEST_ENV", " ")
require.Equal(t, "fallback", envOrDefault("SIGMA_TEST_ENV", "fallback"))
t.Setenv("SIGMA_CREDENTIALS_FILE", "/tmp/sigma-creds.json")
require.Equal(t, "/tmp/sigma-creds.json", defaultCredentialsFile())
}
func TestCommonListQuery(t *testing.T) {
query := commonListQuery("sigma", 2, 50, "created_at", "desc")
require.Equal(t, "sigma", query.Get("name"))
require.Equal(t, "2", query.Get("page"))
require.Equal(t, "50", query.Get("limit"))
require.Equal(t, "created_at", query.Get("sort"))
require.Equal(t, "desc", query.Get("method"))
require.Empty(t, commonListQuery("", 0, 0, "", ""))
}
func TestOptionalFlagValues(t *testing.T) {
command := &cobra.Command{Use: "test"}
addNamespaceWriteFlags(command)
require.NoError(t, command.Flags().Set("description", "desc"))
require.NoError(t, command.Flags().Set("size-limit", "1024"))
require.NoError(t, command.Flags().Set("visibility", enums.VisibilityPrivate.String()))
require.Equal(t, "desc", *optionalString(command, "description"))
require.Equal(t, int64(1024), *optionalInt64(command, "size-limit"))
visibility, err := optionalVisibility(command)
require.NoError(t, err)
require.Equal(t, enums.VisibilityPrivate, *visibility)
require.Nil(t, optionalString(command, "overview"))
invalid := &cobra.Command{Use: "invalid"}
invalid.Flags().String("visibility", "", "")
require.NoError(t, invalid.Flags().Set("visibility", "bad"))
_, err = optionalVisibility(invalid)
require.Error(t, err)
}
func TestFormatErrorBodyAndEndpoint(t *testing.T) {
require.Equal(t, "{\n \"message\": \"bad\"\n}", formatErrorBody([]byte(`{"message":"bad"}`)))
require.Equal(t, "plain error", formatErrorBody([]byte(" plain error ")))
require.Equal(t, "/api/v1/namespaces/ns", endpoint("/api/v1/namespaces/%s", "ns"))
}
func findCommand(t *testing.T, root *cobra.Command, name string) *cobra.Command {
t.Helper()
command, _, err := root.Find([]string{name})
require.NoError(t, err)
return command
}
func executeCommand(t *testing.T, command *cobra.Command, args ...string) string {
t.Helper()
oldStdout := os.Stdout
readFile, writeFile, err := os.Pipe()
require.NoError(t, err)
os.Stdout = writeFile
command.SetArgs(args)
command.SetOut(writeFile)
command.SetErr(writeFile)
err = command.Execute()
require.NoError(t, err)
require.NoError(t, writeFile.Close())
os.Stdout = oldStdout
t.Cleanup(func() {
os.Stdout = oldStdout
})
data, err := io.ReadAll(readFile)
require.NoError(t, err)
require.NoError(t, readFile.Close())
return string(data)
}