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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# toolhive-core

[![Release][release-img]][release] [![Build Status][ci-img]][ci]
[![Coverage Status][coveralls-img]][coveralls]
[![License: Apache 2.0][license-img]][license]
[![Star on GitHub][stars-img]][stars]

The ToolHive Platform common libraries and specifications.

`toolhive-core` provides stable, well-tested Go utilities with explicit API guarantees for the ToolHive ecosystem. Projects like [toolhive](https://github.com/stacklok/toolhive), [dockyard](https://github.com/stacklok/dockyard), [toolhive-registry](https://github.com/stacklok/toolhive-registry), and [toolhive-registry-server](https://github.com/stacklok/toolhive-registry-server) depend on this library for shared functionality.
Expand Down Expand Up @@ -99,3 +104,19 @@ For packages with external dependencies, multiple types, or broader API surface:
## License

Apache-2.0 - See [LICENSE](LICENSE) for details.

<!-- Badge links -->
<!-- prettier-ignore-start -->
[release]: https://github.com/stacklok/toolhive-core/releases/latest
[release-img]: https://img.shields.io/github/v/release/stacklok/toolhive-core
[ci]: https://github.com/stacklok/toolhive-core/actions/workflows/ci.yml
[ci-img]: https://github.com/stacklok/toolhive-core/actions/workflows/ci.yml/badge.svg
[coveralls]: https://coveralls.io/github/stacklok/toolhive-core
[coveralls-img]: https://coveralls.io/repos/github/stacklok/toolhive-core/badge.svg
[license]: https://opensource.org/licenses/Apache-2.0
[license-img]: https://img.shields.io/badge/License-Apache%202.0-blue.svg
[stars]: https://github.com/stacklok/toolhive-core/stargazers
[stars-img]: https://img.shields.io/github/stars/stacklok/toolhive-core?style=social
<!-- prettier-ignore-end -->

<!-- markdownlint-disable-file first-line-heading no-inline-html no-emphasis-as-heading -->
73 changes: 73 additions & 0 deletions cel/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,79 @@ func TestCheckError_Details(t *testing.T) {
assert.NotEmpty(t, checkErr.Errors)
}

func TestEngine_WithMaxExpressionLength(t *testing.T) {
t.Parallel()

t.Run("rejects expression exceeding custom limit", func(t *testing.T) {
t.Parallel()

engine := newTestClaimsEngine().WithMaxExpressionLength(10)

_, err := engine.Compile(`claims["sub"] == "user123"`)
require.Error(t, err)
assert.ErrorIs(t, err, cel.ErrExpressionCheck)
})

t.Run("accepts expression within custom limit", func(t *testing.T) {
t.Parallel()

engine := newTestClaimsEngine().WithMaxExpressionLength(100)

expr, err := engine.Compile(`true`)
require.NoError(t, err)
require.NotNil(t, expr)
})

t.Run("rejects expression at default limit via Check", func(t *testing.T) {
t.Parallel()

engine := newTestClaimsEngine().WithMaxExpressionLength(5)

err := engine.Check(`claims["sub"] == "user123"`)
require.Error(t, err)
assert.ErrorIs(t, err, cel.ErrExpressionCheck)
})
}

func TestEngine_WithCostLimit(t *testing.T) {
t.Parallel()

t.Run("returns engine for chaining", func(t *testing.T) {
t.Parallel()

engine := newTestClaimsEngine().WithCostLimit(500000)
require.NotNil(t, engine)
})

t.Run("compiles and evaluates within cost limit", func(t *testing.T) {
t.Parallel()

engine := newTestClaimsEngine().WithCostLimit(cel.DefaultCostLimit)

expr, err := engine.Compile(`claims["sub"] == "user123"`)
require.NoError(t, err)

ctx := map[string]any{"claims": map[string]any{"sub": "user123"}}
result, err := expr.EvaluateBool(ctx)
require.NoError(t, err)
assert.True(t, result)
})

t.Run("zero cost limit rejects evaluation", func(t *testing.T) {
t.Parallel()

engine := newTestClaimsEngine().WithCostLimit(0)

expr, err := engine.Compile(`claims["sub"] == "user123"`)
require.NoError(t, err)

ctx := map[string]any{"claims": map[string]any{"sub": "user123"}}
_, err = expr.EvaluateBool(ctx)
require.Error(t, err)
assert.ErrorIs(t, err, cel.ErrEvaluation)
})
}

func TestEngine_Concurrency(t *testing.T) {
t.Parallel()

Expand Down
66 changes: 66 additions & 0 deletions container/verifier/verifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
// SPDX-License-Identifier: Apache-2.0

package verifier

import (
"testing"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

registry "github.com/stacklok/toolhive-core/registry/types"
)

// ------ New ------

func TestNew_NilProvenance(t *testing.T) {
t.Parallel()
_, err := New(nil, nil)
assert.ErrorIs(t, err, ErrProvenanceServerInformationNotSet)
}

// ------ WithKeychain ------

func TestWithKeychain_SetsKeychain(t *testing.T) {
t.Parallel()
s := &Sigstore{}
kc := authn.NewMultiKeychain()
got := s.WithKeychain(kc)
assert.Same(t, s, got, "WithKeychain should return the same *Sigstore")
assert.Equal(t, kc, s.keychain)
}

// ------ GetVerificationResults ------

// GetVerificationResults with an unparseable image reference should return an error
// (not ErrProvenanceNotFoundOrIncomplete, so the error propagates directly).
func TestGetVerificationResults_InvalidImageRef(t *testing.T) {
t.Parallel()
s := &Sigstore{keychain: authn.DefaultKeychain}
results, err := s.GetVerificationResults("")
assert.Error(t, err)
assert.Nil(t, results)
}

// ------ VerifyServer ------

// VerifyServer propagates errors from GetVerificationResults.
func TestVerifyServer_PropagatesGetVerificationError(t *testing.T) {
t.Parallel()
s := &Sigstore{keychain: authn.DefaultKeychain}
err := s.VerifyServer("", &registry.Provenance{})
assert.Error(t, err)
assert.NotErrorIs(t, err, ErrImageNotSigned)
assert.NotErrorIs(t, err, ErrProvenanceMismatch)
}

// VerifyServer with a nil provenance still calls GetVerificationResults first;
// if that errors the provenance nil-ness is irrelevant.
func TestVerifyServer_NilProvenance_InvalidRef(t *testing.T) {
t.Parallel()
s := &Sigstore{keychain: authn.DefaultKeychain}
err := s.VerifyServer("", nil)
require.Error(t, err)
}
Loading
Loading