fetch_ml/tests/property/config_properties_test.go
Jeremie Fraeys 80370e9f4a
test(phase-6): property-based tests with gopter
Implement property-based invariant verification:

- TestPropertyConfigHashAlwaysPresent: Valid configs produce non-empty hash
- TestPropertyConfigHashDeterministic: Same config produces same hash
- TestPropertyDetectionSourceAlwaysValid: CreateDetectorWithInfo returns valid source
- TestPropertyProvenanceFailClosed: Strict mode fails on incomplete env
- TestPropertyScanArtifactsNeverNilEnvironment: Artifacts can hold Environment
- TestPropertyManifestEnvironmentSurvivesRoundtrip: Environment survives write/load

Uses gopter for property-based testing with deterministic seeds
2026-02-23 20:25:49 -05:00

118 lines
3.2 KiB
Go

package property
import (
"testing"
"github.com/jfraeys/fetch_ml/internal/worker"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
)
// TestPropertyConfigHashAlwaysPresent verifies that any valid config passing Validate()
// produces a non-empty hash. This is a property-based test using gopter.
func TestPropertyConfigHashAlwaysPresent(t *testing.T) {
parameters := gopter.DefaultTestParameters()
parameters.Rng.Seed(12345) // Deterministic seed for reproducibility
properties := gopter.NewProperties(parameters)
// Property: valid config always has non-empty hash
properties.Property("valid config always produces non-empty hash", prop.ForAll(
func(complianceMode string, maxWorkers, port int, host string) bool {
// Generate a valid config
cfg := &worker.Config{
Host: host,
Port: port,
MaxWorkers: maxWorkers,
ComplianceMode: complianceMode,
GPUVendor: "none",
Sandbox: worker.SandboxConfig{
NetworkMode: "none",
SeccompProfile: "default-hardened",
NoNewPrivileges: true,
},
}
// Apply security defaults
cfg.Sandbox.ApplySecurityDefaults()
// Skip invalid configs (validation would fail)
if err := cfg.Validate(); err != nil {
// Invalid configs are okay for this property - we only care about valid ones
return true
}
// Compute hash
hash, err := cfg.ComputeResolvedConfigHash()
if err != nil {
return false
}
// Property: hash must be non-empty for valid configs
return hash != ""
},
// Generators for inputs
gen.OneConstOf("hipaa", "standard", ""),
gen.IntRange(1, 100),
gen.IntRange(1, 65535),
gen.OneConstOf("localhost", "127.0.0.1", "0.0.0.0"),
))
properties.TestingRun(t)
}
// TestPropertyConfigHashDeterministic verifies that the same config always
// produces the same hash (deterministic property).
func TestPropertyConfigHashDeterministic(t *testing.T) {
parameters := gopter.DefaultTestParameters()
parameters.Rng.Seed(12345)
properties := gopter.NewProperties(parameters)
properties.Property("same config produces same hash", prop.ForAll(
func(host string, port, maxWorkers int, gpuVendor string) bool {
// Create two identical configs
cfg1 := &worker.Config{
Host: host,
Port: port,
MaxWorkers: maxWorkers,
GPUVendor: gpuVendor,
Sandbox: worker.SandboxConfig{
NetworkMode: "none",
SeccompProfile: "default-hardened",
},
}
cfg1.Sandbox.ApplySecurityDefaults()
cfg2 := &worker.Config{
Host: host,
Port: port,
MaxWorkers: maxWorkers,
GPUVendor: gpuVendor,
Sandbox: worker.SandboxConfig{
NetworkMode: "none",
SeccompProfile: "default-hardened",
},
}
cfg2.Sandbox.ApplySecurityDefaults()
// Compute hashes
hash1, err1 := cfg1.ComputeResolvedConfigHash()
hash2, err2 := cfg2.ComputeResolvedConfigHash()
if err1 != nil || err2 != nil {
return false
}
// Property: identical configs must have identical hashes
return hash1 == hash2
},
gen.OneConstOf("localhost", "127.0.0.1"),
gen.IntRange(22, 8080),
gen.IntRange(1, 32),
gen.OneConstOf("nvidia", "amd", "none"),
))
properties.TestingRun(t)
}