fetch_ml/internal/api/helpers/hash_helpers_test.go
Jeremie Fraeys a4e2ecdbe6
refactor: co-locate api, audit, auth tests with source code
Move unit tests from tests/unit/ to internal/ following Go conventions:
- tests/unit/api/* -> internal/api/* (WebSocket handlers, helpers, duplicate detection)
- tests/unit/audit/* -> internal/audit/* (alert, sealed, verifier tests)
- tests/unit/auth/* -> internal/auth/* (API key, keychain, user manager)
- tests/unit/crypto/kms/* -> internal/auth/kms/* (cache, protocol tests)

Update import paths in test files to reflect new locations.

Benefits:
- Tests live alongside the code they test
- Easier navigation and maintenance
- Clearer package boundaries
- Follows standard Go project layout
2026-03-12 16:34:54 -04:00

137 lines
3.6 KiB
Go

package helpers_test
import (
"testing"
"github.com/jfraeys/fetch_ml/internal/api/helpers"
"github.com/jfraeys/fetch_ml/internal/queue"
)
func TestComputeDatasetID(t *testing.T) {
tests := []struct {
name string
datasetSpecs []queue.DatasetSpec
datasets []string
want string
}{
{
name: "both empty",
datasetSpecs: nil,
datasets: nil,
want: "",
},
{
name: "only datasets",
datasetSpecs: nil,
datasets: []string{"dataset1", "dataset2"},
want: "", // will be a hash
},
{
name: "dataset specs with checksums",
datasetSpecs: []queue.DatasetSpec{
{Name: "ds1", Checksum: "abc123"},
{Name: "ds2", Checksum: "def456"},
},
datasets: nil,
want: "", // will be a hash
},
{
name: "dataset specs without checksums",
datasetSpecs: []queue.DatasetSpec{
{Name: "ds1"},
{Name: "ds2"},
},
datasets: nil,
want: "", // will use names
},
{
name: "checksums take precedence",
datasetSpecs: []queue.DatasetSpec{{Name: "ds1", Checksum: "xyz789"}},
datasets: []string{"dataset1"},
want: "", // should use checksum from specs
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := helpers.ComputeDatasetID(tt.datasetSpecs, tt.datasets)
if tt.want == "" {
// Just verify it returns something or empty as expected
if len(tt.datasetSpecs) == 0 && len(tt.datasets) == 0 && got != "" {
t.Errorf("ComputeDatasetID() = %q, want empty string", got)
}
} else if got != tt.want {
t.Errorf("ComputeDatasetID() = %q, want %q", got, tt.want)
}
})
}
}
func TestComputeParamsHash(t *testing.T) {
tests := []struct {
name string
args string
want string
}{
{
name: "empty args",
args: "",
want: "",
},
{
name: "simple args",
args: "--lr 0.01 --epochs 10",
want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // sha256 of trimmed args
},
{
name: "args with spaces",
args: " --lr 0.01 ",
want: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // sha256 of trimmed args
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := helpers.ComputeParamsHash(tt.args)
// Just verify it returns a string (hash computation is deterministic)
if tt.want == "" && got != "" {
t.Errorf("ComputeParamsHash() expected empty, got %q", got)
}
if tt.want != "" && got == "" {
t.Errorf("ComputeParamsHash() expected non-empty hash")
}
})
}
}
func TestComputeParamsHash_Deterministic(t *testing.T) {
args := "--lr 0.01 --epochs 10"
hash1 := helpers.ComputeParamsHash(args)
hash2 := helpers.ComputeParamsHash(args)
if hash1 != hash2 {
t.Error("ComputeParamsHash() should be deterministic")
}
// Different args should produce different hashes
differentArgs := "--lr 0.02 --epochs 10"
differentHash := helpers.ComputeParamsHash(differentArgs)
if hash1 == differentHash {
t.Error("ComputeParamsHash() should produce different hashes for different inputs")
}
}
func TestComputeParamsHash_Whitespace(t *testing.T) {
// Same args with different whitespace should produce the same hash
hash1 := helpers.ComputeParamsHash("--lr 0.01 --epochs 10")
hash2 := helpers.ComputeParamsHash(" --lr 0.01 --epochs 10 ")
hash3 := helpers.ComputeParamsHash("--lr 0.01 --epochs 10")
if hash1 != hash2 {
t.Error("ComputeParamsHash() should handle leading/trailing whitespace consistently")
}
// Note: internal whitespace differences may or may not produce different hashes
// depending on implementation details
_ = hash3
}