frostfs-contract/tests/policy_test.go

193 lines
7.0 KiB
Go

package tests
import (
"bytes"
"path"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
const policyPath = "../policy"
func deployPolicyContract(t *testing.T, e *neotest.Executor) util.Uint160 {
cfgPath := path.Join(policyPath, "config.yml")
c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath)
e.DeployContract(t, c, []any{nil})
return c.Hash
}
func newPolicyInvoker(t *testing.T) *neotest.ContractInvoker {
e := newExecutor(t)
h := deployPolicyContract(t, e)
return e.CommitteeInvoker(h)
}
func TestPolicy(t *testing.T) {
e := newPolicyInvoker(t)
checkChainsIteratorByPrefix(t, e, policy.Namespace, "mynamespace", "ingress", [][]byte{})
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{})
// Policies are opaque to the contract and are just raw bytes to store.
p1 := []byte("chain1")
p2 := []byte("chain2")
p3 := []byte("chain3")
p33 := []byte("chain33")
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
checkChains(t, e, "mynamespace", "", "all", nil)
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains.
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2})
checkChains(t, e, "mynamespace", "cnr1", "all", nil) // No chains attached to 'all'.
checkChains(t, e, "mynamespace", "cnr2", "ingress", [][]byte{p1}) // Only namespace, no chains for the container.
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
checkChain(t, e, policy.Container, "cnr1", "ingress:myrule3", p33)
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p33}) // Override chain.
checkChainsByPrefix(t, e, policy.Container, "cnr1", "", [][]byte{p2, p33})
checkChainsByPrefix(t, e, policy.IAM, "", "", nil)
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
checkChainsIteratorByPrefix(t, e, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
t.Run("removal", func(t *testing.T) {
t.Run("wrong name", func(t *testing.T) {
e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
checkChains(t, e, "mynamespace", "", "ingress", [][]byte{p1})
})
e.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
checkChains(t, e, "mynamespace", "", "ingress", nil)
checkChains(t, e, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
// Remove by prefix.
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
// Remove by prefix.
e.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
checkChains(t, e, "mynamespace", "cnr1", "ingress", nil)
checkTargets(t, e, policy.Namespace, [][]byte{})
checkTargets(t, e, policy.Container, [][]byte{})
})
t.Run("add again after removal", func(t *testing.T) {
e.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
e.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
checkTargets(t, e, policy.Namespace, [][]byte{[]byte("mynamespace")})
checkTargets(t, e, policy.Container, [][]byte{[]byte("cnr1")})
})
}
func TestAutorization(t *testing.T) {
e := newPolicyInvoker(t)
e.Invoke(t, stackitem.Null{}, "getAdmin")
s := e.NewAccount(t, 1_0000_0000)
c := e.WithSigners(s)
args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")}
c.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...)
e.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
c.Invoke(t, stackitem.Null{}, "addChain", args...)
}
func checkChains(t *testing.T, e *neotest.ContractInvoker, namespace, container, name string, expected [][]byte) {
s, err := e.TestInvoke(t, "listChains", namespace, container, name)
require.NoError(t, err)
checksChainsOnStack(t, s, expected)
}
func checkChainsByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) {
s, err := e.TestInvoke(t, "listChainsByPrefix", kind, entityName, prefix)
require.NoError(t, err)
checksChainsOnStack(t, s, expected)
}
func checkChainsIteratorByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) {
s, err := e.TestInvoke(t, "iteratorChainsByPrefix", kind, entityName, prefix)
require.NoError(t, err)
if s.Len() == 0 {
t.Fatal("Stack is empty")
}
iteratorItem := s.Pop().Value().(*storage.Iterator)
policys := iteratorToArray(iteratorItem)
require.Equal(t, len(expected), len(policys))
for i := range expected {
bytesPolicy, err := policys[i].TryBytes()
require.NoError(t, err)
require.Equal(t, expected[i], bytesPolicy)
}
}
func checksChainsOnStack(t *testing.T, s *vm.Stack, expected [][]byte) {
require.Equal(t, 1, s.Len())
var actual [][]byte
arr := s.Pop().Array()
for i := range arr {
bs, err := arr[i].TryBytes()
require.NoError(t, err)
actual = append(actual, bs)
}
require.ElementsMatch(t, expected, actual)
}
func checkChain(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, name string, expected []byte) {
s, err := e.TestInvoke(t, "getChain", kind, entityName, name)
require.NoError(t, err)
require.Equal(t, 1, s.Len())
require.True(t, bytes.Equal(expected, s.Pop().Bytes()))
}
func checkTargets(t *testing.T, e *neotest.ContractInvoker, kind byte, expected [][]byte) {
s, err := e.TestInvoke(t, "listTargets", kind)
require.NoError(t, err)
require.NotEqual(t, 0, s.Len(), "stack is empty")
iteratorItem := s.Pop().Value().(*storage.Iterator)
targets := iteratorToArray(iteratorItem)
require.Equal(t, len(expected), len(targets))
for i := range expected {
bytesTargets, err := targets[i].TryBytes()
require.NoError(t, err)
require.Equal(t, expected[i], bytesTargets)
}
}