Files
releases/internal/storage/mock/mock.go
T
Anders Eknert e43ef0a979 Use any in place of interface{} (#7566)
Earlier this evening I tried to run the Go
[modernize](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize)
analyzer on OPA. That didn't go as planned:

- https://github.com/golang/go/issues/73661
- https://github.com/golang/go/issues/73663

While we wait for that to be fixed, I figured an old-fashioned
search-and-replace across the repo may work for at least the
`interface{}` to `any` conversion. That should help make it easier
to see the other fixes as applied by the modernize tool once it has
had those issues resolved.

Signed-off-by: Anders Eknert <anders@styra.com>
2025-05-12 13:57:48 +02:00

261 lines
7.1 KiB
Go

// Copyright 2019 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
// Package mock defines a fake storage implementation for use in testing.
package mock
import (
"context"
"fmt"
"testing"
"github.com/open-policy-agent/opa/v1/storage"
"github.com/open-policy-agent/opa/v1/storage/inmem"
)
// Transaction is a mock storage.Transaction implementation for use in testing.
// It uses an internal storage.Transaction pointer with some added functionality.
type Transaction struct {
txn storage.Transaction
Committed int
Aborted int
}
// ID returns the underlying transaction ID
func (t *Transaction) ID() uint64 {
return t.txn.ID()
}
// Validate returns an error if the transaction is in an invalid state
func (t *Transaction) Validate() error {
if t.Committed > 1 {
return fmt.Errorf("transaction %d has too many commits (%d)", t.ID(), t.Committed)
}
if t.Aborted > 1 {
return fmt.Errorf("transaction %d has too many aborts (%d)", t.ID(), t.Committed)
}
return nil
}
func (t *Transaction) safeToUse() bool {
return t.Committed == 0 && t.Aborted == 0
}
// Store is a mock storage.Store implementation for use in testing.
type Store struct {
inmem storage.Store
storeOpts []inmem.Opt
baseData map[string]any
Transactions []*Transaction
Reads []*ReadCall
Writes []*WriteCall
}
// ReadCall captures the parameters for a Read call
type ReadCall struct {
Transaction *Transaction
Path storage.Path
Error error
Safe bool
}
// WriteCall captures the parameters for a write call
type WriteCall struct {
Transaction *Transaction
Op storage.PatchOp
Path storage.Path
Error error
Safe bool
}
// New creates a new mock Store
func New(opt ...inmem.Opt) *Store {
s := &Store{
storeOpts: opt,
}
s.Reset()
return s
}
// NewWithData creates a store with some initial data
func NewWithData(data map[string]any, opt ...inmem.Opt) *Store {
s := &Store{
baseData: data,
storeOpts: opt,
}
s.Reset()
return s
}
// Reset the store
func (s *Store) Reset() {
s.Transactions = []*Transaction{}
s.Reads = []*ReadCall{}
s.Writes = []*WriteCall{}
if s.baseData != nil {
s.inmem = inmem.NewFromObjectWithOpts(s.baseData, s.storeOpts...)
} else {
s.inmem = inmem.NewWithOpts(s.storeOpts...)
}
}
// GetTransaction will a transaction with a specific ID
// that was associated with this Store.
func (s *Store) GetTransaction(id uint64) *Transaction {
for _, txn := range s.Transactions {
if txn.ID() == id {
return txn
}
}
return nil
}
// Errors returns a list of errors for each invalid state found.
// If any Transactions are invalid or reads/writes were
// unsafe an error will be returned for each problem.
func (s *Store) Errors() []error {
var errs []error
for _, txn := range s.Transactions {
err := txn.Validate()
if err != nil {
errs = append(errs, err)
}
}
for _, read := range s.Reads {
if !read.Safe {
errs = append(errs, fmt.Errorf("unsafe Read call %+v", *read))
}
}
for _, write := range s.Writes {
if !write.Safe {
errs = append(errs, fmt.Errorf("unsafe Write call %+v", *write))
}
}
return errs
}
// AssertValid will raise an error with the provided testing.T if
// there are any errors on the store.
func (s *Store) AssertValid(t *testing.T) {
t.Helper()
for _, err := range s.Errors() {
t.Errorf("Error detected on store: %s", err)
}
}
// storage.Store interface implementation
// Register just shims the call to the underlying inmem store
func (s *Store) Register(ctx context.Context, txn storage.Transaction, config storage.TriggerConfig) (storage.TriggerHandle, error) {
return s.inmem.Register(ctx, getRealTxn(txn), config)
}
// ListPolicies just shims the call to the underlying inmem store
func (s *Store) ListPolicies(ctx context.Context, txn storage.Transaction) ([]string, error) {
return s.inmem.ListPolicies(ctx, getRealTxn(txn))
}
// GetPolicy just shims the call to the underlying inmem store
func (s *Store) GetPolicy(ctx context.Context, txn storage.Transaction, name string) ([]byte, error) {
return s.inmem.GetPolicy(ctx, getRealTxn(txn), name)
}
// UpsertPolicy just shims the call to the underlying inmem store
func (s *Store) UpsertPolicy(ctx context.Context, txn storage.Transaction, name string, policy []byte) error {
return s.inmem.UpsertPolicy(ctx, getRealTxn(txn), name, policy)
}
// DeletePolicy just shims the call to the underlying inmem store
func (s *Store) DeletePolicy(ctx context.Context, txn storage.Transaction, name string) error {
return s.inmem.DeletePolicy(ctx, getRealTxn(txn), name)
}
// NewTransaction will create a new transaction on the underlying inmem store
// but wraps it with a mock Transaction. These are then tracked on the store.
func (s *Store) NewTransaction(ctx context.Context, params ...storage.TransactionParams) (storage.Transaction, error) {
realTxn, err := s.inmem.NewTransaction(ctx, params...)
if err != nil {
return nil, err
}
txn := &Transaction{
txn: realTxn,
Committed: 0,
Aborted: 0,
}
s.Transactions = append(s.Transactions, txn)
return txn, nil
}
// Read will make a read from the underlying inmem store and
// add a new entry to the mock store Reads list. If there
// is an error are the read is unsafe it will be noted in
// the ReadCall.
func (s *Store) Read(ctx context.Context, txn storage.Transaction, path storage.Path) (any, error) {
mockTxn := txn.(*Transaction)
data, err := s.inmem.Read(ctx, mockTxn.txn, path)
s.Reads = append(s.Reads, &ReadCall{
Transaction: mockTxn,
Path: path,
Error: err,
Safe: mockTxn.safeToUse(),
})
return data, err
}
// Write will make a read from the underlying inmem store and
// add a new entry to the mock store Writes list. If there
// is an error are the write is unsafe it will be noted in
// the WriteCall.
func (s *Store) Write(ctx context.Context, txn storage.Transaction, op storage.PatchOp, path storage.Path, value any) error {
mockTxn := txn.(*Transaction)
err := s.inmem.Write(ctx, mockTxn.txn, op, path, value)
s.Writes = append(s.Writes, &WriteCall{
Transaction: mockTxn,
Op: op,
Path: path,
Error: err,
Safe: mockTxn.safeToUse(),
})
return nil
}
// Commit will commit the underlying transaction while
// also updating the mock Transaction
func (s *Store) Commit(ctx context.Context, txn storage.Transaction) error {
mockTxn := txn.(*Transaction)
err := s.inmem.Commit(ctx, mockTxn.txn)
if err != nil {
return err
}
mockTxn.Committed++
return nil
}
// Abort will abort the underlying transaction while
// also updating the mock Transaction
func (s *Store) Abort(ctx context.Context, txn storage.Transaction) {
mockTxn := txn.(*Transaction)
s.inmem.Abort(ctx, mockTxn.txn)
mockTxn.Aborted++
}
func (s *Store) Truncate(ctx context.Context, txn storage.Transaction, params storage.TransactionParams, it storage.Iterator) error {
return s.inmem.Truncate(ctx, getRealTxn(txn), params, it)
}
func getRealTxn(txn storage.Transaction) storage.Transaction {
return txn.(*Transaction).txn
}