package tests import ( "errors" "path" "testing" "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "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/nspcc-dev/neo-go/pkg/vm/vmstate" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) const frostfsidPath = "../frostfsid" const defaultNamespace = "" const ( setAdminMethod = "setAdmin" getAdminMethod = "getAdmin" clearAdminMethod = "clearAdmin" createSubjectMethod = "createSubject" getSubjectMethod = "getSubject" listSubjectsMethod = "listSubjects" addSubjectKeyMethod = "addSubjectKey" removeSubjectKeyMethod = "removeSubjectKey" getSubjectByKeyMethod = "getSubjectByKey" getSubjectKeyByNameMethod = "getSubjectKeyByName" setSubjectNameMethod = "setSubjectName" setSubjectKVMethod = "setSubjectKV" deleteSubjectKVMethod = "deleteSubjectKV" deleteSubjectMethod = "deleteSubject" createNamespaceMethod = "createNamespace" getNamespaceMethod = "getNamespace" getNamespaceExtendedMethod = "getNamespaceExtended" listNamespacesMethod = "listNamespaces" listNamespaceSubjectsMethod = "listNamespaceSubjects" createGroupMethod = "createGroup" getGroupMethod = "getGroup" getGroupExtendedMethod = "getGroupExtended" getGroupIDByNameMethod = "getGroupIDByName" setGroupNameMethod = "setGroupName" setGroupKVMethod = "setGroupKV" deleteGroupKVMethod = "deleteGroupKV" listGroupsMethod = "listGroups" addSubjectToGroupMethod = "addSubjectToGroup" removeSubjectFromGroupMethod = "removeSubjectFromGroup" listGroupSubjectsMethod = "listGroupSubjects" deleteGroupMethod = "deleteGroup" ) const notWitnessedError = "not witnessed" type testFrostFSIDInvoker struct { e *neotest.Executor contractHash util.Uint160 owner *wallet.Account } func (f *testFrostFSIDInvoker) OwnerInvoker() *neotest.ContractInvoker { return f.e.NewInvoker(f.contractHash, neotest.NewSingleSigner(f.owner)) } func (f *testFrostFSIDInvoker) CommitteeInvoker() *neotest.ContractInvoker { return f.e.CommitteeInvoker(f.contractHash) } func (f *testFrostFSIDInvoker) AnonInvoker(t *testing.T) *neotest.ContractInvoker { acc, err := wallet.NewAccount() require.NoError(t, err) return f.e.NewInvoker(f.contractHash, newSigner(t, f.CommitteeInvoker(), acc)) } func newSigner(t *testing.T, c *neotest.ContractInvoker, acc *wallet.Account) neotest.Signer { amount := int64(100_0000_0000) tx := c.NewTx(t, []neotest.Signer{c.Validator}, c.NativeHash(t, nativenames.Gas), "transfer", c.Validator.ScriptHash(), acc.Contract.ScriptHash(), amount, nil) c.AddNewBlock(t, tx) c.CheckHalt(t, tx.Hash()) return neotest.NewSingleSigner(acc) } func deployFrostFSIDContract(t *testing.T, e *neotest.Executor, contractOwner util.Uint160) util.Uint160 { args := make([]any, 5) args[0] = contractOwner c := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml")) e.DeployContract(t, c, args) return c.Hash } func newFrostFSIDInvoker(t *testing.T) *testFrostFSIDInvoker { e := newExecutor(t) acc, err := wallet.NewAccount() require.NoError(t, err) h := deployFrostFSIDContract(t, e, acc.ScriptHash()) newSigner(t, e.CommitteeInvoker(h), acc) return &testFrostFSIDInvoker{ e: e, contractHash: h, owner: acc, } } func TestFrostFSID_ContractOwnersManagement(t *testing.T) { f := newFrostFSIDInvoker(t) anonInvoker := f.AnonInvoker(t) anonInvokerHash := anonInvoker.Signers[0].ScriptHash() invoker := f.OwnerInvoker() invokerHash := invoker.Signers[0].ScriptHash() committeeInvoker := f.CommitteeInvoker() checkOwner(t, anonInvoker, invokerHash) anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace") invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, "namespace") t.Run("setAdmin is only allowed for committee", func(t *testing.T) { invoker.InvokeFail(t, notWitnessedError, setAdminMethod, anonInvokerHash) }) t.Run("replace owner", func(t *testing.T) { committeeInvoker.Invoke(t, stackitem.Null{}, setAdminMethod, anonInvokerHash) checkOwner(t, anonInvoker, anonInvokerHash) invoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace2") anonInvoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, "namespace2") }) t.Run("remove owner", func(t *testing.T) { committeeInvoker.Invoke(t, stackitem.Null{}, clearAdminMethod) checkOwner(t, anonInvoker) invoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace3") anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace3") }) } func checkOwner(t *testing.T, invoker *neotest.ContractInvoker, owner ...util.Uint160) { if len(owner) > 1 { require.Fail(t, "invalid testcase") } s, err := invoker.TestInvoke(t, getAdminMethod) require.NoError(t, err) require.Equal(t, 1, s.Len(), "unexpected number items on stack") if len(owner) == 0 { _, isMissing := s.Pop().Item().(stackitem.Null) require.True(t, isMissing) return } bs, err := s.Pop().Item().TryBytes() require.NoError(t, err) require.Equal(t, bs, owner[0].BytesBE()) } func TestFrostFSID_DefaultNamespace(t *testing.T) { f := newFrostFSIDInvoker(t) subjKey, err := keys.NewPrivateKey() require.NoError(t, err) subjKeyAddr := subjKey.PublicKey().GetScriptHash() invoker := f.OwnerInvoker() groupID := int64(1) groupName := "group" invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, defaultNamespace, groupName) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKeyAddr, groupID) invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjKeyAddr, groupID) invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, defaultNamespace, groupID) invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr) } func TestFrostFSID_SubjectManagement(t *testing.T) { f := newFrostFSIDInvoker(t) subjKey, err := keys.NewPrivateKey() require.NoError(t, err) subjKeyAddr := subjKey.PublicKey().GetScriptHash() anonInvoker := f.AnonInvoker(t) invoker := f.OwnerInvoker() anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) invoker.InvokeFail(t, "already exists", createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes()) s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) require.NoError(t, err) subj := parseSubject(t, s) require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey)) t.Run("add subject key", func(t *testing.T) { subjNewKey, err := keys.NewPrivateKey() require.NoError(t, err) anonInvoker.InvokeFail(t, notWitnessedError, addSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes()) s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) require.NoError(t, err) subj := parseSubject(t, s) require.Len(t, subj.AdditionalKeys, 1) require.True(t, subjNewKey.PublicKey().Equal(subj.AdditionalKeys[0])) t.Run("get subject by additional key", func(t *testing.T) { s, err = anonInvoker.TestInvoke(t, getSubjectByKeyMethod, subjNewKey.PublicKey().Bytes()) require.NoError(t, err) subj := parseSubject(t, s) require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same") s, err = anonInvoker.TestInvoke(t, getSubjectByKeyMethod, subjKey.PublicKey().Bytes()) require.NoError(t, err) subj = parseSubject(t, s) require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same") t.Run("with GetSubject", func(t *testing.T) { s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKey.PublicKey().GetScriptHash()) require.NoError(t, err) subj = parseSubject(t, s) require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same") }) t.Run("remove subject key", func(t *testing.T) { anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes()) anonInvoker.InvokeFail(t, "not found", getSubjectByKeyMethod, subjNewKey.PublicKey().Bytes()) }) }) }) t.Run("set subject name", func(t *testing.T) { login := "some-login" anonInvoker.InvokeFail(t, notWitnessedError, setSubjectNameMethod, subjKeyAddr, login) invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr, login) s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) require.NoError(t, err) subj = parseSubject(t, s) require.Equal(t, login, subj.Name) }) t.Run("set subject KVs", func(t *testing.T) { iamPath := "iam/path" anonInvoker.InvokeFail(t, notWitnessedError, setSubjectKVMethod, subjKeyAddr, client.IAMPathKey, iamPath) invoker.Invoke(t, stackitem.Null{}, setSubjectKVMethod, subjKeyAddr, client.IAMPathKey, iamPath) s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) require.NoError(t, err) subj = parseSubject(t, s) require.Equal(t, iamPath, subj.KV[client.IAMPathKey]) anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectKVMethod, subjKeyAddr, client.IAMPathKey) invoker.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, subjKeyAddr, client.IAMPathKey) s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr) require.NoError(t, err) subj = parseSubject(t, s) require.Empty(t, subj.KV) }) t.Run("list subjects", func(t *testing.T) { newSubjKey, err := keys.NewPrivateKey() require.NoError(t, err) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, newSubjKey.PublicKey().Bytes()) s, err = anonInvoker.TestInvoke(t, listSubjectsMethod) require.NoError(t, err) addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil) require.NoError(t, err) require.Len(t, addresses, 2) require.ElementsMatch(t, addresses, []util.Uint160{subjKeyAddr, newSubjKey.PublicKey().GetScriptHash()}) }) anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectMethod, subjKeyAddr) invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr) anonInvoker.InvokeFail(t, "subject not found", getSubjectMethod, subjKeyAddr) } func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) { f := newFrostFSIDInvoker(t) subjName1 := "subj1" subjKey1, err := keys.NewPrivateKey() require.NoError(t, err) subjKeyAddr1 := subjKey1.PublicKey().GetScriptHash() subjName2 := "subj2" subjKey2, err := keys.NewPrivateKey() require.NoError(t, err) subjKeyAddr2 := subjKey2.PublicKey().GetScriptHash() subjName3 := "subj3" subjKey3, err := keys.NewPrivateKey() require.NoError(t, err) subjKeyAddr3 := subjKey3.PublicKey().GetScriptHash() invoker := f.OwnerInvoker() ns1, ns2 := "ns1", "ns2" invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1) invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey1.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey2.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns2, subjKey3.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName3) // Check that we can find public key by name for subj1 (with name) // and cannot find key for subj2 (without name) s, err := invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName1) checkPublicKeyResult(t, s, err, subjKey1) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2) checkPublicKeyResult(t, s, err, nil) // Check that we can find public key for by name for subj2 when we set its name invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr2, subjName2) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2) checkPublicKeyResult(t, s, err, subjKey2) // Check that we cannot set for second subject name that the first subject has already taken invoker.InvokeFail(t, "not available", setSubjectNameMethod, subjKeyAddr2, subjName1) // Check that we cannot find public key by name for subject that was removed invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr2) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2) checkPublicKeyResult(t, s, err, nil) // Check that subj3 can have the same name as subj1 if they belong to different namespaces // Also check that after subject renaming its key cannot be found by old name invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName1) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName1) checkPublicKeyResult(t, s, err, subjKey3) s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName3) checkPublicKeyResult(t, s, err, nil) } func TestFrostFSIS_GroupNameRelatedInvariants(t *testing.T) { f := newFrostFSIDInvoker(t) groupName1, groupName2 := "group1", "group2" groupID1, groupID2 := int64(1), int64(2) invoker := f.OwnerInvoker() ns1, ns2 := "ns1", "ns2" // Create two group // Create two namespace. // Add these groups to ns1 invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1) invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2) invoker.Invoke(t, stackitem.Make(groupID1), createGroupMethod, ns1, groupName1) invoker.Invoke(t, stackitem.Make(groupID2), createGroupMethod, ns1, groupName2) // Check that we can find group id by name for group1 in ns1 and not in ns2 s, err := invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName1) checkGroupIDResult(t, s, err, groupID1) s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns2, groupName1) checkGroupIDResult(t, s, err, -1) // Check that we cannot set for second group name that the first subject has already taken invoker.InvokeFail(t, "not available", setGroupNameMethod, ns1, groupID1, groupName2) // Check that we cannot create group with the same name in namespace, but can in another invoker.InvokeFail(t, "not available", createGroupMethod, ns1, groupName2) invoker.Invoke(t, stackitem.Make(3), createGroupMethod, ns2, groupName2) // Check that we cannot find group id by name for group that was removed invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, ns1, groupID2) s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName2) checkGroupIDResult(t, s, err, -1) // Check that we can create group with the name that was freed after deleting invoker.Invoke(t, stackitem.Make(4), createGroupMethod, ns1, groupName2) // Check that after group renaming its id cannot be found by old name newGroupName := "new" invoker.Invoke(t, stackitem.Null{}, setGroupNameMethod, ns1, groupID1, newGroupName) s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName1) checkGroupIDResult(t, s, err, -1) s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, newGroupName) checkGroupIDResult(t, s, err, groupID1) } func TestFrostFSID_NamespaceManagement(t *testing.T) { f := newFrostFSIDInvoker(t) anonInvoker := f.AnonInvoker(t) invoker := f.OwnerInvoker() namespace := "some-namespace" anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, namespace) invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace) invoker.InvokeFail(t, "already exists", createNamespaceMethod, namespace) s, err := anonInvoker.TestInvoke(t, getNamespaceMethod, namespace) require.NoError(t, err) ns := parseNamespace(t, s.Pop().Item()) require.Equal(t, namespace, ns.Name) t.Run("add user to namespace", func(t *testing.T) { subjKey, err := keys.NewPrivateKey() require.NoError(t, err) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns.Name, subjKey.PublicKey().Bytes()) subjName := "name" subjAddress := subjKey.PublicKey().GetScriptHash() invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjAddress, subjName) s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress) require.NoError(t, err) subj := parseSubject(t, s) require.Equal(t, namespace, subj.Namespace) t.Run("list namespace subjects", func(t *testing.T) { s, err := anonInvoker.TestInvoke(t, listNamespaceSubjectsMethod, namespace) require.NoError(t, err) addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil) require.NoError(t, err) require.ElementsMatch(t, addresses, []util.Uint160{subjAddress}) }) t.Run("get subject key by name", func(t *testing.T) { s, err := anonInvoker.TestInvoke(t, getSubjectKeyByNameMethod, namespace, subjName) require.NoError(t, err) foundKey, err := unwrap.PublicKey(makeValidRes(s.Pop().Item()), nil) require.NoError(t, err) require.Equal(t, subjKey.PublicKey(), foundKey) }) t.Run("list namespaces", func(t *testing.T) { namespace2 := "some-namespace2" invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace2) s, err := anonInvoker.TestInvoke(t, listNamespacesMethod) require.NoError(t, err) namespaces := parseNamespaces(t, readIteratorAll(s)) require.NoError(t, err) require.ElementsMatch(t, namespaces, []Namespace{{Name: defaultNamespace}, {Name: namespace}, {Name: namespace2}}) t.Run("find namespaces with some subjects", func(t *testing.T) { for _, ns := range namespaces { s, err := anonInvoker.TestInvoke(t, getNamespaceExtendedMethod, ns.Name) require.NoError(t, err) nsExt := parseNamespaceExtended(t, s.Pop().Item()) if nsExt.SubjectsCount > 0 { require.Equal(t, namespace, nsExt.Name) } } }) }) }) } func TestFrostFSID_GroupManagement(t *testing.T) { f := newFrostFSIDInvoker(t) anonInvoker := f.AnonInvoker(t) invoker := f.OwnerInvoker() nsName := "namespace" invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, nsName) groupID := int64(1) groupName := "group" anonInvoker.InvokeFail(t, notWitnessedError, createGroupMethod, nsName, groupName) invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, nsName, groupName) s, err := anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID) require.NoError(t, err) group := parseGroup(t, s.Pop().Item()) require.Equal(t, groupID, group.ID) require.Equal(t, groupName, group.Name) s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName) require.NoError(t, err) groups := parseGroups(t, readIteratorAll(s)) require.ElementsMatch(t, groups, []Group{{ID: groupID, Name: groupName, Namespace: nsName}}) t.Run("add subjects to group", func(t *testing.T) { subjKey, err := keys.NewPrivateKey() require.NoError(t, err) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes()) subjAddress := subjKey.PublicKey().GetScriptHash() anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupID) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupID) t.Run("list group subjects", func(t *testing.T) { s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupID) require.NoError(t, err) addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil) require.NoError(t, err) require.ElementsMatch(t, addresses, []util.Uint160{subjAddress}) anonInvoker.InvokeFail(t, "not witnessed", removeSubjectFromGroupMethod, subjAddress, groupID) invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjAddress, groupID) s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupID) require.NoError(t, err) addresses, err = unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil) require.NoError(t, err) require.Empty(t, addresses) t.Run("get group extended", func(t *testing.T) { subjectsCount := 10 for i := 0; i < subjectsCount; i++ { subjKey, err := keys.NewPrivateKey() require.NoError(t, err) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupID) } s, err = anonInvoker.TestInvoke(t, getGroupExtendedMethod, nsName, groupID) require.NoError(t, err) groupExt := parseGroupExtended(t, s.Pop().Item()) require.Equal(t, groupID, groupExt.ID) require.Equal(t, groupName, groupExt.Name) require.Empty(t, groupExt.KV) require.EqualValues(t, subjectsCount, groupExt.SubjectsCount) }) }) }) t.Run("set group name", func(t *testing.T) { newGroupName := "new-name" anonInvoker.InvokeFail(t, notWitnessedError, setGroupNameMethod, nsName, groupID, newGroupName) invoker.Invoke(t, stackitem.Null{}, setGroupNameMethod, nsName, groupID, newGroupName) s, err = anonInvoker.TestInvoke(t, getGroupIDByNameMethod, nsName, newGroupName) require.NoError(t, err) require.Equal(t, groupID, s.Pop().BigInt().Int64()) s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID) require.NoError(t, err) group = parseGroup(t, s.Pop().Item()) require.Equal(t, newGroupName, group.Name) }) t.Run("set group KVs", func(t *testing.T) { iamARN := "arn" anonInvoker.InvokeFail(t, notWitnessedError, setGroupKVMethod, nsName, groupID, client.IAMARNKey, iamARN) invoker.Invoke(t, stackitem.Null{}, setGroupKVMethod, nsName, groupID, client.IAMARNKey, iamARN) s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID) require.NoError(t, err) group = parseGroup(t, s.Pop().Item()) require.Equal(t, iamARN, group.KV[client.IAMARNKey]) anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupKVMethod, nsName, groupID, client.IAMARNKey) invoker.Invoke(t, stackitem.Null{}, deleteGroupKVMethod, nsName, groupID, client.IAMARNKey) s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID) require.NoError(t, err) group = parseGroup(t, s.Pop().Item()) require.Empty(t, group.KV) }) t.Run("delete group", func(t *testing.T) { anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupMethod, nsName, groupID) invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, nsName, groupID) anonInvoker.InvokeFail(t, "group not found", getGroupMethod, nsName, groupID) s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName) require.NoError(t, err) groups := parseGroups(t, readIteratorAll(s)) require.Empty(t, groups) }) } func TestAdditionalKeyFromPrimarySubject(t *testing.T) { f := newFrostFSIDInvoker(t) invoker := f.OwnerInvoker() subjAPrimaryKey, err := keys.NewPrivateKey() require.NoError(t, err) subjAKeyAddr := subjAPrimaryKey.PublicKey().GetScriptHash() subjBPrimaryKey, err := keys.NewPrivateKey() require.NoError(t, err) subjBKeyAddr := subjBPrimaryKey.PublicKey().GetScriptHash() subjCPrimaryKey, err := keys.NewPrivateKey() require.NoError(t, err) subjDPrimaryKey, err := keys.NewPrivateKey() require.NoError(t, err) subjFPrimaryKey, err := keys.NewPrivateKey() require.NoError(t, err) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjAPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjBPrimaryKey.PublicKey().Bytes()) invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjAPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjCPrimaryKey.PublicKey().Bytes()) invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjCPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjBKeyAddr) invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjFPrimaryKey.PublicKey().Bytes()) invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjFPrimaryKey.PublicKey().GetScriptHash()) invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjFPrimaryKey.PublicKey().Bytes()) } func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) { if key == nil { require.ErrorContains(t, err, "not found") return } require.NoError(t, err) foundKey, err := unwrap.PublicKey(makeValidRes(s.Pop().Item()), nil) require.NoError(t, err) require.Equal(t, key.PublicKey(), foundKey) } func checkGroupIDResult(t *testing.T, s *vm.Stack, err error, groupID int64) { if groupID == -1 { require.ErrorContains(t, err, "not found") return } require.NoError(t, err) foundGroupID, err := unwrap.Int64(makeValidRes(s.Pop().Item()), nil) require.NoError(t, err) require.Equal(t, groupID, foundGroupID) } func readIteratorAll(s *vm.Stack) []stackitem.Item { iter := s.Pop().Value().(*storage.Iterator) stackItems := make([]stackitem.Item, 0) for iter.Next() { stackItems = append(stackItems, iter.Value()) } return stackItems } type Subject struct { PrimaryKey keys.PublicKey AdditionalKeys keys.PublicKeys Namespace string Name string KV map[string]string } func parseSubject(t *testing.T, s *vm.Stack) Subject { var subj Subject subjStruct := s.Pop().Array() require.Len(t, subjStruct, 5) pkBytes, err := subjStruct[0].TryBytes() require.NoError(t, err) err = subj.PrimaryKey.DecodeBytes(pkBytes) require.NoError(t, err) if !subjStruct[1].Equals(stackitem.Null{}) { subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(subjStruct[1]), nil) require.NoError(t, err) } nsBytes, err := subjStruct[2].TryBytes() require.NoError(t, err) subj.Namespace = string(nsBytes) nameBytes, err := subjStruct[3].TryBytes() require.NoError(t, err) subj.Name = string(nameBytes) subj.KV, err = parseMap(subjStruct[4]) require.NoError(t, err) return subj } func parseMap(item stackitem.Item) (map[string]string, error) { if item.Equals(stackitem.Null{}) { return nil, nil } metaMap, err := unwrap.Map(makeValidRes(item), nil) if err != nil { return nil, err } meta, ok := metaMap.Value().([]stackitem.MapElement) if !ok { return nil, errors.New("invalid map type") } res := make(map[string]string, len(meta)) for _, element := range meta { key, err := element.Key.TryBytes() if err != nil { return nil, err } val, err := element.Value.TryBytes() if err != nil { return nil, err } res[string(key)] = string(val) } return res, nil } type Namespace struct { Name string } type NamespaceExtended struct { Name string SubjectsCount int64 GroupsCount int64 } func parseNamespace(t *testing.T, item stackitem.Item) Namespace { var ns Namespace subjStruct := item.Value().([]stackitem.Item) require.Len(t, subjStruct, 1) nameBytes, err := subjStruct[0].TryBytes() require.NoError(t, err) ns.Name = string(nameBytes) return ns } func parseNamespaceExtended(t *testing.T, item stackitem.Item) NamespaceExtended { var ns NamespaceExtended subjStruct := item.Value().([]stackitem.Item) require.Len(t, subjStruct, 3) nameBytes, err := subjStruct[0].TryBytes() require.NoError(t, err) ns.Name = string(nameBytes) groupCountInt, err := subjStruct[1].TryInteger() require.NoError(t, err) ns.GroupsCount = groupCountInt.Int64() subjectsCountInt, err := subjStruct[2].TryInteger() require.NoError(t, err) ns.SubjectsCount = subjectsCountInt.Int64() return ns } func parseNamespaces(t *testing.T, items []stackitem.Item) []Namespace { res := make([]Namespace, len(items)) for i := 0; i < len(items); i++ { res[i] = parseNamespace(t, items[i]) } return res } type Group struct { ID int64 Name string Namespace string KV map[string]string } type GroupExtended struct { ID int64 Name string Namespace string KV map[string]string SubjectsCount int64 } func parseGroup(t *testing.T, item stackitem.Item) Group { var group Group subjStruct := item.Value().([]stackitem.Item) require.Len(t, subjStruct, 4) groupID, err := subjStruct[0].TryInteger() require.NoError(t, err) group.ID = groupID.Int64() nameBytes, err := subjStruct[1].TryBytes() require.NoError(t, err) group.Name = string(nameBytes) namespaceBytes, err := subjStruct[2].TryBytes() require.NoError(t, err) group.Namespace = string(namespaceBytes) group.KV, err = parseMap(subjStruct[3]) require.NoError(t, err) return group } func parseGroupExtended(t *testing.T, item stackitem.Item) GroupExtended { var gr GroupExtended subjStruct := item.Value().([]stackitem.Item) require.Len(t, subjStruct, 5) groupID, err := subjStruct[0].TryInteger() require.NoError(t, err) gr.ID = groupID.Int64() nameBytes, err := subjStruct[1].TryBytes() require.NoError(t, err) gr.Name = string(nameBytes) namespaceBytes, err := subjStruct[2].TryBytes() require.NoError(t, err) gr.Namespace = string(namespaceBytes) gr.KV, err = parseMap(subjStruct[3]) require.NoError(t, err) subjectsCountInt, err := subjStruct[4].TryInteger() require.NoError(t, err) gr.SubjectsCount = subjectsCountInt.Int64() return gr } func parseGroups(t *testing.T, items []stackitem.Item) []Group { res := make([]Group, len(items)) for i := 0; i < len(items); i++ { res[i] = parseGroup(t, items[i]) } return res } func makeValidRes(item stackitem.Item) *result.Invoke { return &result.Invoke{ Stack: []stackitem.Item{item}, State: vmstate.Halt.String(), } }