Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions core/bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/Clyra-AI/proof/core/signing"
Expand Down Expand Up @@ -73,3 +74,204 @@ func TestSignManifestCosignRequiresKeyPath(t *testing.T) {
_, err := SignManifestCosign(Manifest{Files: []ManifestEntry{}}, "")
require.ErrorContains(t, err, "cosign key path is required")
}

func TestReadManifestBranches(t *testing.T) {
_, err := ReadManifest(t.TempDir())
require.Error(t, err)

dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte("{"), 0o644))
_, err = ReadManifest(dir)
require.Error(t, err)
}

func TestWriteManifestError(t *testing.T) {
err := WriteManifest(filepath.Join(t.TempDir(), "missing"), Manifest{})
require.Error(t, err)
}

func TestVerifyHashMismatchAndSignatureBranches(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "records.jsonl"), []byte("{}\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"files":[{"path":"records.jsonl","sha256":"sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}]}`), 0o644))
_, err := Verify(dir, VerifyOpts{})
require.ErrorContains(t, err, "bundle hash mismatch")

require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"files":[{"path":"records.jsonl","sha256":"sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356"}]}`), 0o644))
_, err = Verify(dir, VerifyOpts{VerifySignatures: true})
require.ErrorContains(t, err, "has no signatures")

require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{
"files":[{"path":"records.jsonl","sha256":"sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356"}],
"signatures":[{"alg":"ed25519","key_id":"k","sig":"x","signed_digest":"d"}]
}`), 0o644))
_, err = Verify(dir, VerifyOpts{VerifySignatures: true})
require.ErrorContains(t, err, "public key is required")

require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{
"files":[{"path":"records.jsonl","sha256":"sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356"}],
"signatures":[{"alg":"rsa","key_id":"k","sig":"x","signed_digest":"d"}]
}`), 0o644))
_, err = Verify(dir, VerifyOpts{VerifySignatures: true})
require.ErrorContains(t, err, "unsupported bundle signature algorithm")
}

func TestVerifyAdditionalBranches(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"files":[{"path":"missing.jsonl","sha256":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}]}`), 0o644))
_, err := Verify(dir, VerifyOpts{})
require.Error(t, err)

require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"files":[{"path":"missing.jsonl"}]}`), 0o644))
_, err = Verify(dir, VerifyOpts{})
require.Error(t, err)

recordsDir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(recordsDir, "records.jsonl"), []byte("{}\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(recordsDir, "manifest.json"), []byte(`{"files":[{"path":"records.jsonl","sha256":"sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356"}]}`), 0o644))
key1, err := signing.GenerateKey()
require.NoError(t, err)
_, err = SignFile(recordsDir, key1)
require.NoError(t, err)
key2, err := signing.GenerateKey()
require.NoError(t, err)
_, err = Verify(recordsDir, VerifyOpts{
VerifySignatures: true,
PublicKey: signing.PublicKey{Public: key2.Public},
})
require.Error(t, err)
}

func TestSignManifestAndSignFileErrorBranches(t *testing.T) {
_, err := SignManifest(Manifest{Files: []ManifestEntry{}}, signing.SigningKey{})
require.ErrorContains(t, err, "private key is required")

_, err = SignFile(t.TempDir(), signing.SigningKey{})
require.Error(t, err)

dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"files":[]}`), 0o644))
_, err = SignFileCosign(dir, "")
require.ErrorContains(t, err, "cosign key path is required")

if runtime.GOOS != "windows" {
key, genErr := signing.GenerateKey()
require.NoError(t, genErr)

readonly := t.TempDir()
manifestPath := filepath.Join(readonly, "manifest.json")
require.NoError(t, os.WriteFile(manifestPath, []byte(`{"files":[]}`), 0o644))
require.NoError(t, os.Chmod(manifestPath, 0o444))
defer func() { _ = os.Chmod(manifestPath, 0o644) }()

_, err = SignFile(readonly, key)
require.Error(t, err)
}
}

func TestVerifyCosignSignatureMissingMaterial(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "records.jsonl"), []byte("{}\n"), 0o644))
manifest := Manifest{
Files: []ManifestEntry{
{
Path: "records.jsonl",
SHA256: "sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
},
},
}
digest, err := ManifestDigest(manifest)
require.NoError(t, err)
manifest.Signatures = []signing.Signature{
{
Alg: "cosign",
KeyID: "cosign:test",
Sig: "ZmFrZS1zaWduYXR1cmU=",
SignedDigest: digest,
},
}
raw, err := json.Marshal(manifest)
require.NoError(t, err)
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), raw, 0o644))

_, err = Verify(dir, VerifyOpts{VerifySignatures: true})
require.ErrorContains(t, err, "requires --cosign-key or --cosign-cert")
}

func TestSignManifestCosignAndSignFileCosignSuccess(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("fake cosign shell helper is unix-only")
}
fakeBinDir := writeFakeCosign(t)
t.Setenv("PATH", fakeBinDir+":"+os.Getenv("PATH"))

signed, err := SignManifestCosign(Manifest{Files: []ManifestEntry{}}, "fake.key")
require.NoError(t, err)
require.Len(t, signed.Signatures, 1)
require.Equal(t, "cosign", signed.Signatures[0].Alg)

dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{"files":[]}`), 0o644))
out, err := SignFileCosign(dir, "fake.key")
require.NoError(t, err)
require.Len(t, out.Signatures, 1)

persisted, err := ReadManifest(dir)
require.NoError(t, err)
require.Len(t, persisted.Signatures, 1)
}

func TestVerifyCosignSignaturePath(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("fake cosign shell helper is unix-only")
}
fakeBinDir := writeFakeCosign(t)
t.Setenv("PATH", fakeBinDir+":"+os.Getenv("PATH"))

dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "records.jsonl"), []byte("{}\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`{
"files":[{"path":"records.jsonl","sha256":"sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356"}]
}`), 0o644))
signed, err := SignFileCosign(dir, "fake.key")
require.NoError(t, err)
require.Len(t, signed.Signatures, 1)

_, err = Verify(dir, VerifyOpts{
VerifySignatures: true,
Cosign: signing.CosignVerifyOpts{
KeyPath: "fake.key",
},
})
require.NoError(t, err)
}

func writeFakeCosign(t *testing.T) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "cosign")
script := `#!/usr/bin/env sh
set -eu
mode="$1"
shift
if [ "$mode" = "sign-blob" ]; then
out=""
while [ "$#" -gt 0 ]; do
if [ "$1" = "--output-signature" ]; then
out="$2"
shift 2
continue
fi
shift
done
printf "ZmFrZS1zaWduYXR1cmU=\n" > "$out"
exit 0
fi
if [ "$mode" = "verify-blob" ]; then
exit 0
fi
exit 0
`
require.NoError(t, os.WriteFile(path, []byte(script), 0o755))
return dir
}
54 changes: 54 additions & 0 deletions core/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package errors

import (
stdliberrors "errors"
"testing"

"github.com/stretchr/testify/require"
)

func TestNewAndOptions(t *testing.T) {
err := New(
KindValidation,
"schema.invalid",
"schema validation failed",
WithField("event"),
WithPath("v1/types/decision.schema.json"),
)
require.Error(t, err)

typed, ok := As(err)
require.True(t, ok)
require.Equal(t, KindValidation, typed.Kind)
require.Equal(t, "schema.invalid", typed.Code)
require.Equal(t, "schema validation failed", typed.Message)
require.Equal(t, "event", typed.Field)
require.Equal(t, "v1/types/decision.schema.json", typed.Path)
require.Equal(t, "schema validation failed", typed.Error())
}

func TestWrapAndUnwrap(t *testing.T) {
root := stdliberrors.New("low-level failure")
err := Wrap(KindInternal, "record.marshal", "marshal payload", root)
require.Error(t, err)
require.Equal(t, "marshal payload: low-level failure", err.Error())
require.True(t, stdliberrors.Is(err, root))
}

func TestAsNotTyped(t *testing.T) {
typed, ok := As(stdliberrors.New("plain"))
require.False(t, ok)
require.Nil(t, typed)
}

func TestNilErrorBehaviors(t *testing.T) {
var typed *Error
require.Equal(t, "<nil>", typed.Error())
require.Nil(t, typed.Unwrap())
}

func TestErrorFallsBackToCauseMessage(t *testing.T) {
root := stdliberrors.New("fallback")
err := New(KindInternal, "x", "", WithCause(root))
require.EqualError(t, err, "fallback")
}
Loading