Merge pull request #32 from Snider/feature-add-go-vet
Feature add go vet
This commit is contained in:
commit
248de1e9df
7 changed files with 266 additions and 93 deletions
10
.github/workflows/go.yml
vendored
10
.github/workflows/go.yml
vendored
|
|
@ -23,8 +23,14 @@ jobs:
|
|||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v -coverprofile=coverage.out ./...
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Test (race + coverage)
|
||||
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
|
||||
|
||||
- name: Fuzz (10s)
|
||||
run: go test -run=Fuzz -fuzz=Fuzz -fuzztime=10s ./pkg/trix
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ tasks:
|
|||
test:
|
||||
desc: "Run all tests and generate a coverage report"
|
||||
cmds:
|
||||
- go vet ./...
|
||||
- go test -v -coverprofile=coverage.out ./...
|
||||
|
||||
build:
|
||||
|
|
|
|||
218
cmd/trix/main.go
218
cmd/trix/main.go
|
|
@ -8,7 +8,34 @@ import (
|
|||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
"github.com/Snider/Enchantrix/pkg/enchantrix"
|
||||
"github.com/Snider/Enchantrix/pkg/trix"
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "trix",
|
||||
Short: "A tool for encoding and decoding .trix files",
|
||||
Long: `trix is a command-line tool for working with the .trix file format, which is used for storing encrypted data.`,
|
||||
}
|
||||
|
||||
encodeCmd = &cobra.Command{
|
||||
Use: "encode",
|
||||
Short: "Encode a file to the .trix format",
|
||||
RunE: runEncode,
|
||||
}
|
||||
|
||||
decodeCmd = &cobra.Command{
|
||||
Use: "decode",
|
||||
Short: "Decode a .trix file",
|
||||
RunE: runDecode,
|
||||
}
|
||||
|
||||
hashCmd = &cobra.Command{
|
||||
Use: "hash [algorithm]",
|
||||
Short: "Hash a file using a specified algorithm",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runHash,
|
||||
}
|
||||
)
|
||||
|
||||
var availableSigils = []string{
|
||||
|
|
@ -18,131 +45,138 @@ var availableSigils = []string{
|
|||
"blake2s-256", "blake2b-256", "blake2b-384", "blake2b-512",
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := clir.NewCli("trix", "A tool for encoding and decoding .trix files", "v0.0.1")
|
||||
var exit = os.Exit
|
||||
|
||||
// Encode command
|
||||
encodeCmd := app.NewSubCommand("encode", "Encode a file to the .trix format")
|
||||
var encodeInput, encodeOutput, encodeMagic string
|
||||
encodeCmd.StringFlag("input", "Input file (or stdin)", &encodeInput)
|
||||
encodeCmd.StringFlag("output", "Output file", &encodeOutput)
|
||||
encodeCmd.StringFlag("magic", "Magic number (4 bytes)", &encodeMagic)
|
||||
encodeCmd.Action(func() error {
|
||||
sigils := encodeCmd.OtherArgs()
|
||||
return handleEncode(encodeInput, encodeOutput, encodeMagic, sigils)
|
||||
})
|
||||
func init() {
|
||||
// Add flags to encode command
|
||||
encodeCmd.Flags().StringP("input", "i", "", "Input file (or stdin)")
|
||||
encodeCmd.Flags().StringP("output", "o", "", "Output file")
|
||||
encodeCmd.Flags().StringP("magic", "m", "", "Magic number (4 bytes)")
|
||||
|
||||
// Decode command
|
||||
decodeCmd := app.NewSubCommand("decode", "Decode a .trix file")
|
||||
var decodeInput, decodeOutput, decodeMagic string
|
||||
decodeCmd.StringFlag("input", "Input file (or stdin)", &decodeInput)
|
||||
decodeCmd.StringFlag("output", "Output file", &decodeOutput)
|
||||
decodeCmd.StringFlag("magic", "Magic number (4 bytes)", &decodeMagic)
|
||||
decodeCmd.Action(func() error {
|
||||
sigils := decodeCmd.OtherArgs()
|
||||
return handleDecode(decodeInput, decodeOutput, decodeMagic, sigils)
|
||||
})
|
||||
// Add flags to decode command
|
||||
decodeCmd.Flags().StringP("input", "i", "", "Input file (or stdin)")
|
||||
decodeCmd.Flags().StringP("output", "o", "", "Output file")
|
||||
decodeCmd.Flags().StringP("magic", "m", "", "Magic number (4 bytes)")
|
||||
|
||||
// Hash command
|
||||
hashCmd := app.NewSubCommand("hash", "Hash a file using a specified algorithm")
|
||||
var hashInput string
|
||||
var hashAlgo string
|
||||
hashCmd.StringFlag("input", "Input file (or stdin)", &hashInput)
|
||||
hashCmd.Action(func() error {
|
||||
algo := hashCmd.OtherArgs()
|
||||
if len(algo) > 0 {
|
||||
hashAlgo = algo[0]
|
||||
// Add flags to hash command
|
||||
hashCmd.Flags().StringP("input", "i", "", "Input file (or stdin)")
|
||||
|
||||
rootCmd.AddCommand(encodeCmd, decodeCmd, hashCmd)
|
||||
|
||||
// Add sigil commands
|
||||
for _, sigilName := range availableSigils {
|
||||
sigilCmd := &cobra.Command{
|
||||
Use: sigilName,
|
||||
Short: "Apply the " + sigilName + " sigil",
|
||||
RunE: createSigilRunE(sigilName),
|
||||
}
|
||||
return handleHash(hashInput, hashAlgo)
|
||||
})
|
||||
|
||||
// Sigil commands
|
||||
for _, sigil := range availableSigils {
|
||||
sigil := sigil // capture range variable
|
||||
sigilCmd := app.NewSubCommand(sigil, "Apply the "+sigil+" sigil")
|
||||
var input string
|
||||
sigilCmd.StringFlag("input", "Input file or string (or stdin)", &input)
|
||||
sigilCmd.Action(func() error {
|
||||
return handleSigil(sigil, input)
|
||||
})
|
||||
}
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
sigilCmd.Flags().StringP("input", "i", "-", "Input file or string (or stdin)")
|
||||
rootCmd.AddCommand(sigilCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func readInput(inputFile string) ([]byte, error) {
|
||||
if inputFile == "" {
|
||||
return ioutil.ReadAll(os.Stdin)
|
||||
func createSigilRunE(sigilName string) func(cmd *cobra.Command, args []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
input, _ := cmd.Flags().GetString("input")
|
||||
return handleSigil(cmd, sigilName, input)
|
||||
}
|
||||
return ioutil.ReadFile(inputFile)
|
||||
}
|
||||
|
||||
func handleSigil(sigilName, input string) error {
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func runEncode(cmd *cobra.Command, args []string) error {
|
||||
input, _ := cmd.Flags().GetString("input")
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
magic, _ := cmd.Flags().GetString("magic")
|
||||
return handleEncode(cmd, input, output, magic, args)
|
||||
}
|
||||
|
||||
func runDecode(cmd *cobra.Command, args []string) error {
|
||||
input, _ := cmd.Flags().GetString("input")
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
magic, _ := cmd.Flags().GetString("magic")
|
||||
return handleDecode(cmd, input, output, magic, args)
|
||||
}
|
||||
|
||||
func runHash(cmd *cobra.Command, args []string) error {
|
||||
input, _ := cmd.Flags().GetString("input")
|
||||
return handleHash(cmd, input, args[0])
|
||||
}
|
||||
|
||||
func handleSigil(cmd *cobra.Command, sigilName, input string) error {
|
||||
s, err := enchantrix.NewSigil(sigilName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data []byte
|
||||
// check if input is a file or a string
|
||||
if _, err := os.Stat(input); err == nil {
|
||||
data, err = readInput(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if input == "-" {
|
||||
data, err = ioutil.ReadAll(cmd.InOrStdin())
|
||||
} else if _, err := os.Stat(input); err == nil {
|
||||
data, err = ioutil.ReadFile(input)
|
||||
} else {
|
||||
if input == "" {
|
||||
data, err = readInput("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
data = []byte(input)
|
||||
}
|
||||
data = []byte(input)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := s.In(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(out))
|
||||
cmd.OutOrStdout().Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleHash(inputFile, algo string) error {
|
||||
func handleHash(cmd *cobra.Command, inputFile, algo string) error {
|
||||
if algo == "" {
|
||||
return fmt.Errorf("hash algorithm is required")
|
||||
}
|
||||
service := crypt.NewService()
|
||||
if !service.IsHashAlgo(algo) {
|
||||
return fmt.Errorf("invalid hash algorithm: %s", algo)
|
||||
}
|
||||
|
||||
data, err := readInput(inputFile)
|
||||
var data []byte
|
||||
var err error
|
||||
if inputFile == "" || inputFile == "-" {
|
||||
data, err = ioutil.ReadAll(cmd.InOrStdin())
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(inputFile)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service := crypt.NewService()
|
||||
hash := service.Hash(crypt.HashType(algo), string(data))
|
||||
fmt.Println(hash)
|
||||
cmd.OutOrStdout().Write([]byte(hash))
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleEncode(inputFile, outputFile, magicNumber string, sigils []string) error {
|
||||
if outputFile == "" {
|
||||
return fmt.Errorf("output file is required")
|
||||
}
|
||||
func handleEncode(cmd *cobra.Command, inputFile, outputFile, magicNumber string, sigils []string) error {
|
||||
if len(magicNumber) != 4 {
|
||||
return fmt.Errorf("magic number must be 4 bytes long")
|
||||
}
|
||||
|
||||
payload, err := readInput(inputFile)
|
||||
var data []byte
|
||||
var err error
|
||||
if inputFile == "" || inputFile == "-" {
|
||||
data, err = ioutil.ReadAll(cmd.InOrStdin())
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(inputFile)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := &trix.Trix{
|
||||
Header: make(map[string]interface{}),
|
||||
Payload: payload,
|
||||
Payload: data,
|
||||
InSigils: sigils,
|
||||
}
|
||||
|
||||
|
|
@ -155,31 +189,39 @@ func handleEncode(inputFile, outputFile, magicNumber string, sigils []string) er
|
|||
return err
|
||||
}
|
||||
|
||||
if outputFile == "" || outputFile == "-" {
|
||||
_, err = cmd.OutOrStdout().Write(encoded)
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(outputFile, encoded, 0644)
|
||||
}
|
||||
|
||||
func handleDecode(inputFile, outputFile, magicNumber string, sigils []string) error {
|
||||
if outputFile == "" {
|
||||
return fmt.Errorf("output file is required")
|
||||
}
|
||||
func handleDecode(cmd *cobra.Command, inputFile, outputFile, magicNumber string, sigils []string) error {
|
||||
if len(magicNumber) != 4 {
|
||||
return fmt.Errorf("magic number must be 4 bytes long")
|
||||
}
|
||||
|
||||
data, err := readInput(inputFile)
|
||||
var data []byte
|
||||
var err error
|
||||
if inputFile == "" || inputFile == "-" {
|
||||
data, err = ioutil.ReadAll(cmd.InOrStdin())
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(inputFile)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t, err := trix.Decode(data, magicNumber, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.OutSigils = sigils
|
||||
if err := t.Unpack(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outputFile == "" || outputFile == "-" {
|
||||
_, err = cmd.OutOrStdout().Write(t.Payload)
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(outputFile, t.Payload, 0644)
|
||||
}
|
||||
|
|
|
|||
106
cmd/trix/main_test.go
Normal file
106
cmd/trix/main_test.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// executeCommand executes the root command with the given arguments and returns the output.
|
||||
func executeCommand(args ...string) (string, error) {
|
||||
b := new(bytes.Buffer)
|
||||
rootCmd.SetOut(b)
|
||||
rootCmd.SetErr(b)
|
||||
rootCmd.SetArgs(args)
|
||||
err := rootCmd.Execute()
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
// executeCommandWithStdin executes the root command with the given arguments and stdin,
|
||||
// and returns the output.
|
||||
func executeCommandWithStdin(stdin string, args ...string) (string, error) {
|
||||
b := new(bytes.Buffer)
|
||||
rootCmd.SetOut(b)
|
||||
rootCmd.SetErr(b)
|
||||
rootCmd.SetIn(strings.NewReader(stdin))
|
||||
rootCmd.SetArgs(args)
|
||||
err := rootCmd.Execute()
|
||||
// reset stdin
|
||||
rootCmd.SetIn(os.Stdin)
|
||||
return b.String(), err
|
||||
}
|
||||
|
||||
func TestRootCommand(t *testing.T) {
|
||||
output, err := executeCommand()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, output, "trix [command]")
|
||||
}
|
||||
|
||||
func TestEncodeDecodeCommand(t *testing.T) {
|
||||
// 1. Create original payload
|
||||
originalPayload := "hello world"
|
||||
inputFile, _ := os.CreateTemp("", "input")
|
||||
defer os.Remove(inputFile.Name())
|
||||
inputFile.Write([]byte(originalPayload))
|
||||
inputFile.Close()
|
||||
|
||||
// 2. Encode it to a file
|
||||
encodedFile, _ := os.CreateTemp("", "encoded")
|
||||
defer os.Remove(encodedFile.Name())
|
||||
_, err := executeCommand("encode", "-i", inputFile.Name(), "-o", encodedFile.Name(), "-m", "magc", "reverse")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 3. Decode it back
|
||||
decodedFile, _ := os.CreateTemp("", "decoded")
|
||||
defer os.Remove(decodedFile.Name())
|
||||
_, err = executeCommand("decode", "-i", encodedFile.Name(), "-o", decodedFile.Name(), "-m", "magc", "reverse")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 4. Verify content
|
||||
finalPayload, err := os.ReadFile(decodedFile.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, originalPayload, string(finalPayload))
|
||||
}
|
||||
|
||||
func TestHashCommand(t *testing.T) {
|
||||
// Test with input file
|
||||
inputFile, _ := os.CreateTemp("", "input")
|
||||
defer os.Remove(inputFile.Name())
|
||||
inputFile.Write([]byte("hello"))
|
||||
inputFile.Close()
|
||||
output, err := executeCommand("hash", "md5", "-i", inputFile.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "5d41402abc4b2a76b9719d911017c592", strings.TrimSpace(output))
|
||||
|
||||
// Test with stdin
|
||||
output, err = executeCommandWithStdin("hello", "hash", "md5")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "5d41402abc4b2a76b9719d911017c592", strings.TrimSpace(output))
|
||||
|
||||
// Test error cases
|
||||
_, err = executeCommand("hash")
|
||||
assert.Error(t, err)
|
||||
_, err = executeCommand("hash", "invalid-algo")
|
||||
assert.Error(t, err)
|
||||
_, err = executeCommand("hash", "md5", "-i", "nonexistent-file")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMainFunction(t *testing.T) {
|
||||
// This test is to ensure the main function is covered
|
||||
// We run it in a separate process to avoid os.Exit calls
|
||||
if os.Getenv("GO_TEST_MAIN") == "1" {
|
||||
main()
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], "-test.run=TestMainFunction")
|
||||
cmd.Env = append(os.Environ(), "GO_TEST_MAIN=1")
|
||||
err := cmd.Run()
|
||||
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
|
||||
t.Fatalf("main function exited with error: %v", err)
|
||||
}
|
||||
}
|
||||
4
go.mod
4
go.mod
|
|
@ -3,14 +3,16 @@ module github.com/Snider/Enchantrix
|
|||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/leaanthony/clir v1.7.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/crypto v0.43.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
10
go.sum
10
go.sum
|
|
@ -1,9 +1,15 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/leaanthony/clir v1.7.0 h1:xiAnhl7ryPwuH3ERwPWZp/pCHk8wTeiwuAOt6MiNyAw=
|
||||
github.com/leaanthony/clir v1.7.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
|
|
|
|||
|
|
@ -39,6 +39,16 @@ const (
|
|||
|
||||
// --- Hashing ---
|
||||
|
||||
// IsHashAlgo checks if a string is a valid hash algorithm.
|
||||
func (s *Service) IsHashAlgo(algo string) bool {
|
||||
switch HashType(algo) {
|
||||
case LTHN, SHA512, SHA256, SHA1, MD5:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Hash computes a hash of the payload using the specified algorithm.
|
||||
func (s *Service) Hash(lib HashType, payload string) string {
|
||||
switch lib {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue