From 8affa716e032eb5b5e1b3479f96770fe122733e0 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 7 Nov 2023 13:58:42 +0300 Subject: [PATCH] [#48] frostfsid: Add client tests Signed-off-by: Denis Kirillov --- tests/frostfsid_client_test.go | 503 +++++++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 tests/frostfsid_client_test.go diff --git a/tests/frostfsid_client_test.go b/tests/frostfsid_client_test.go new file mode 100644 index 0000000..1122545 --- /dev/null +++ b/tests/frostfsid_client_test.go @@ -0,0 +1,503 @@ +package tests + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + "testing" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/rpcclient" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/require" +) + +type testFrostFSIDClientInvoker struct { + base *testFrostFSIDInvoker + cli *client.Client + a awaiter +} + +func initFrostfsIFClientTest(t *testing.T) (clientInvoker *testFrostFSIDClientInvoker, cancelFn func()) { + f := newFrostFSIDInvoker(t) + + wlt := initTmpWallet(t) + ctx, cancel := context.WithCancel(context.Background()) + address := runRPC(ctx, t, f.e.Chain, wlt) + + cli, rpc := frostfsidRPCClient(t, address, f.contractHash, f.owner) + + clientInvoker = &testFrostFSIDClientInvoker{ + base: f, + cli: cli, + a: awaiter{ + ctx: ctx, + t: t, + rpc: rpc, + }, + } + + return clientInvoker, func() { + cancel() + err := os.Remove(wlt) + require.NoError(t, err) + } +} + +func frostfsidRPCClient(t *testing.T, address string, contractHash util.Uint160, accs ...*wallet.Account) (*client.Client, *rpcclient.Client) { + rpcCli, err := rpcclient.New(context.Background(), "http://"+address, rpcclient.Options{}) + require.NoError(t, err) + + var acc *wallet.Account + if len(accs) == 0 { + acc, err = wallet.NewAccount() + require.NoError(t, err) + } else { + require.Len(t, accs, 1) + acc = accs[0] + } + + cli, err := client.New(rpcCli, acc, contractHash, nil) + require.NoError(t, err) + + return cli, rpcCli +} + +func TestFrostFSID_Client_ContractOwnersManagement(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + committeeInvoker := ffsid.base.CommitteeInvoker() + defaultOwnerAddress := ffsid.base.owner.ScriptHash() + _, newOwnerAddress := newKey(t) + + checkListOwnersClient(t, ffsid.cli, defaultOwnerAddress) + + _, _, err := ffsid.cli.AddOwner(newOwnerAddress) + require.ErrorContains(t, err, "not witnessed") + committeeInvoker.Invoke(t, stackitem.Null{}, addOwnerMethod, newOwnerAddress) + + checkListOwnersClient(t, ffsid.cli, defaultOwnerAddress, newOwnerAddress) + + _, _, err = ffsid.cli.DeleteOwner(newOwnerAddress) + require.ErrorContains(t, err, "not witnessed") + committeeInvoker.Invoke(t, stackitem.Null{}, deleteOwnerMethod, newOwnerAddress) + + checkListOwnersClient(t, ffsid.cli, defaultOwnerAddress) +} + +func newKey(t *testing.T) (*keys.PrivateKey, util.Uint160) { + key, err := keys.NewPrivateKey() + require.NoError(t, err) + return key, key.PublicKey().GetScriptHash() +} + +func checkListOwnersClient(t *testing.T, cli *client.Client, owners ...util.Uint160) { + addresses, err := cli.ListOwners() + require.NoError(t, err) + require.ElementsMatch(t, addresses, owners) +} + +func TestFrostFSID_Client_SubjectManagement(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + subjKey, subjAddr := newKey(t) + extraKey, _ := newKey(t) + subjLogin := "subj-login" + iamPathKV := "iam/path" + + ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) + ffsid.a.await(ffsid.cli.SetSubjectName(subjAddr, subjLogin)) + ffsid.a.await(ffsid.cli.SetSubjectKV(subjAddr, client.SubjectIAMPathKey, iamPathKV)) + ffsid.a.await(ffsid.cli.AddSubjectKey(subjAddr, extraKey.PublicKey())) + + subj, err := ffsid.cli.GetSubject(subjAddr) + require.NoError(t, err) + require.True(t, subjKey.PublicKey().Equal(subj.PrimaryKey)) + require.Equal(t, subj.Name, subjLogin) + require.Equal(t, map[string]string{client.SubjectIAMPathKey: iamPathKV}, subj.KV) + require.ElementsMatch(t, []*keys.PublicKey{extraKey.PublicKey()}, subj.AdditionalKeys) + + ffsid.a.await(ffsid.cli.DeleteSubjectKV(subjAddr, client.SubjectIAMPathKey)) + subj, err = ffsid.cli.GetSubject(subjAddr) + require.NoError(t, err) + require.Empty(t, subj.KV) + + subjects, err := ffsid.cli.ListSubjects() + require.NoError(t, err) + require.ElementsMatch(t, subjects, []util.Uint160{subjAddr}) + + subj, err = ffsid.cli.GetSubjectByKey(extraKey.PublicKey()) + require.NoError(t, err) + require.True(t, subjKey.PublicKey().Equal(subj.PrimaryKey)) + + ffsid.a.await(ffsid.cli.RemoveSubjectKey(subjAddr, extraKey.PublicKey())) + _, err = ffsid.cli.GetSubjectByKey(extraKey.PublicKey()) + require.ErrorContains(t, err, "not found") + + ffsid.a.await(ffsid.cli.DeleteSubject(subjAddr)) + + _, err = ffsid.cli.GetSubject(subjAddr) + require.ErrorContains(t, err, "not found") + subjects, err = ffsid.cli.ListSubjects() + require.NoError(t, err) + require.Empty(t, subjects) +} + +func TestFrostFSID_Client_NamespaceManagement(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + subjKey, subjAddr := newKey(t) + ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) + + namespace := "namespace" + ffsid.a.await(ffsid.cli.CreateNamespace(namespace)) + _, _, err := ffsid.cli.CreateNamespace(namespace) + require.ErrorContains(t, err, "already exists") + + ns, err := ffsid.cli.GetNamespace(namespace) + require.NoError(t, err) + require.Equal(t, namespace, ns.Name) + + namespaces, err := ffsid.cli.ListNamespaces() + require.NoError(t, err) + require.ElementsMatch(t, []*client.Namespace{ns}, namespaces) + + ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)) + _, _, err = ffsid.cli.AddSubjectToNamespace(subjAddr, namespace) + require.ErrorContains(t, err, "already added") + + subj, err := ffsid.cli.GetSubject(subjAddr) + require.NoError(t, err) + require.Equal(t, namespace, subj.Namespace) + + subjects, err := ffsid.cli.ListNamespaceSubjects(namespace) + require.NoError(t, err) + require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects) + + ffsid.a.await(ffsid.cli.RemoveSubjectFromNamespace(subjAddr)) + + subj, err = ffsid.cli.GetSubject(subjAddr) + require.NoError(t, err) + require.Empty(t, subj.Namespace) + + subjects, err = ffsid.cli.ListNamespaceSubjects(namespace) + require.NoError(t, err) + require.Empty(t, subjects) +} + +func TestFrostFSID_Client_GroupManagement(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + subjKey, subjAddr := newKey(t) + ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey())) + + namespace := "namespace" + ffsid.a.await(ffsid.cli.CreateNamespace(namespace)) + + groupName := "group" + ffsid.a.await(ffsid.cli.CreateGroup(namespace, groupName)) + _, _, err := ffsid.cli.CreateGroup(namespace, groupName) + require.ErrorContains(t, err, "already exists") + + group, err := ffsid.cli.GetGroup(namespace, groupName) + require.NoError(t, err) + require.Equal(t, groupName, group.Name) + + groupExt, err := ffsid.cli.GetGroupExtended(namespace, groupName) + require.NoError(t, err) + require.Zero(t, groupExt.SubjectsCount) + + ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)) + ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupName)) + + subjExt, err := ffsid.cli.GetSubjectExtended(subjAddr) + require.NoError(t, err) + require.ElementsMatch(t, []*client.Group{{Name: groupName, Namespace: namespace}}, subjExt.Groups) + + groupExt, err = ffsid.cli.GetGroupExtended(namespace, groupName) + require.NoError(t, err) + require.EqualValues(t, 1, groupExt.SubjectsCount) + + subjects, err := ffsid.cli.ListGroupSubjects(namespace, groupName) + require.NoError(t, err) + require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects) + + subjects, err = ffsid.cli.ListNamespaceSubjects(namespace) + require.NoError(t, err) + require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects) + + groups, err := ffsid.cli.ListGroups(namespace) + require.NoError(t, err) + require.ElementsMatch(t, []*client.Group{{Name: groupName, Namespace: namespace}}, groups) + + ffsid.a.await(ffsid.cli.DeleteGroup(namespace, groupName)) + _, err = ffsid.cli.GetGroup(namespace, groupName) + require.ErrorContains(t, err, "not found") + + groups, err = ffsid.cli.ListGroups(namespace) + require.NoError(t, err) + require.Empty(t, groups) +} + +type testSubject struct { + key *keys.PrivateKey + addr util.Uint160 +} + +func TestFrostFSID_Client_Lists(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + namespaces := []string{"empty-ns0", "ns1", "ns2", "ns3"} + + tx := ffsid.cli.StartTx() + for _, namespace := range namespaces { + err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace)) + require.NoError(t, err) + } + ffsid.a.await(ffsid.cli.SendTx(tx)) + + subjects := make([]testSubject, 10) + tx = ffsid.cli.StartTx() + for i := range subjects { + subjects[i].key, subjects[i].addr = newKey(t) + err := tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey())) + require.NoError(t, err) + } + ffsid.a.await(ffsid.cli.SendTx(tx)) + + groups := []client.Group{ + { + Name: "empty-group0", + Namespace: namespaces[0], + }, + { + Name: "empty-group1", + Namespace: namespaces[1], + }, + { + Name: "group2", + Namespace: namespaces[1], + }, + { + Name: "group3", + Namespace: namespaces[2], + }, + { + Name: "group4", + Namespace: namespaces[3], + }, + { + Name: "group5", + Namespace: namespaces[3], + }, + } + + tx = ffsid.cli.StartTx() + for _, group := range groups { + err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name)) + require.NoError(t, err) + } + ffsid.a.await(ffsid.cli.SendTx(tx)) + + addSubjectsToNamespaces(t, ffsid, namespaces, subjects) + addSubjectsToGroups(t, ffsid, groups, subjects) + + nsList, err := ffsid.cli.ListNamespaces() + require.NoError(t, err) + require.ElementsMatch(t, []*client.Namespace{{Name: namespaces[0]}, {Name: namespaces[1]}, {Name: namespaces[2]}, {Name: namespaces[3]}}, nsList) + + nonEmptyNs, err := ffsid.cli.ListNonEmptyNamespaces() + require.NoError(t, err) + require.ElementsMatch(t, namespaces[1:], nonEmptyNs) + + checkNamespaceSubjects(t, ffsid.cli, namespaces[0], subjects, 0, 0) + checkNamespaceSubjects(t, ffsid.cli, namespaces[1], subjects, 0, 3) + checkNamespaceSubjects(t, ffsid.cli, namespaces[2], subjects, 3, 5) + checkNamespaceSubjects(t, ffsid.cli, namespaces[3], subjects, 5, 10) + + nonEmptyGroups, err := ffsid.cli.ListNonEmptyGroups(namespaces[1]) + require.NoError(t, err) + require.ElementsMatch(t, []string{groups[2].Name}, nonEmptyGroups) + + checkGroupSubjects(t, ffsid.cli, groups[0], subjects, 0, 0) + checkGroupSubjects(t, ffsid.cli, groups[1], subjects, 0, 0) + checkGroupSubjects(t, ffsid.cli, groups[2], subjects, 2, 3) + checkGroupSubjects(t, ffsid.cli, groups[3], subjects, 3, 5) + checkGroupSubjects(t, ffsid.cli, groups[4], subjects, 6, 8) + checkGroupSubjects(t, ffsid.cli, groups[5], subjects, 8, 10) +} + +func addSubjectsToNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, namespaces []string, subjects []testSubject) { + cli := ffsid.cli + + tx := cli.StartTx() + for _, subject := range subjects[:3] { + err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[1])) + require.NoError(t, err) + } + ffsid.a.await(cli.SendTx(tx)) + + tx = cli.StartTx() + for _, subject := range subjects[3:5] { + err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[2])) + require.NoError(t, err) + } + ffsid.a.await(cli.SendTx(tx)) + + tx = cli.StartTx() + for _, subject := range subjects[5:] { + err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[3])) + require.NoError(t, err) + } + ffsid.a.await(cli.SendTx(tx)) +} + +func addSubjectsToGroups(t *testing.T, ffsid *testFrostFSIDClientInvoker, groups []client.Group, subjects []testSubject) { + cli := ffsid.cli + tx := cli.StartTx() + + err := tx.WrapCall(cli.AddSubjectToGroupCall(subjects[2].addr, groups[2].Name)) + require.NoError(t, err) + + err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[3].addr, groups[3].Name)) + require.NoError(t, err) + err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[4].addr, groups[3].Name)) + require.NoError(t, err) + + ffsid.a.await(cli.SendTx(tx)) + + // we have to start new tx because of insufficient gas / gas limit exceeded error + tx = cli.StartTx() + + err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[6].addr, groups[4].Name)) + require.NoError(t, err) + err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[7].addr, groups[4].Name)) + require.NoError(t, err) + err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[8].addr, groups[5].Name)) + require.NoError(t, err) + err = tx.WrapCall(cli.AddSubjectToGroupCall(subjects[9].addr, groups[5].Name)) + require.NoError(t, err) + + ffsid.a.await(cli.SendTx(tx)) +} + +func checkNamespaceSubjects(t *testing.T, cli *client.Client, ns string, subjects []testSubject, start, end int) { + nsSubjects, err := cli.ListNamespaceSubjects(ns) + require.NoError(t, err) + require.ElementsMatch(t, subjSlice(subjects, start, end), nsSubjects) +} + +func checkGroupSubjects(t *testing.T, cli *client.Client, group client.Group, subjects []testSubject, start, end int) { + groupSubjects, err := cli.ListGroupSubjects(group.Namespace, group.Name) + require.NoError(t, err) + require.ElementsMatch(t, subjSlice(subjects, start, end), groupSubjects) +} + +func subjSlice(subjects []testSubject, start, end int) []util.Uint160 { + res := make([]util.Uint160, 0, end-start) + for i := start; i < end; i++ { + res = append(res, subjects[i].addr) + } + return res +} + +func TestFrostFSID_Client_UseCaseWithS3GW(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + namespace := "namespace" + login := "login" + dataUserKey, dataUserAddr := newKey(t) + extraDataUserKey, _ := newKey(t) + + // admin + tx := ffsid.cli.StartTx() + err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace)) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.CreateSubjectCall(dataUserKey.PublicKey())) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(dataUserAddr, login)) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.AddSubjectKeyCall(dataUserAddr, extraDataUserKey.PublicKey())) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(dataUserAddr, namespace)) + require.NoError(t, err) + ffsid.a.await(ffsid.cli.SendTx(tx)) + + // s3-gw + subj, err := ffsid.cli.GetSubjectByKey(extraDataUserKey.PublicKey()) + require.NoError(t, err) + require.Equal(t, login, subj.Name) + require.True(t, dataUserKey.PublicKey().Equal(subj.PrimaryKey)) + require.Equal(t, namespace, subj.Namespace) +} + +func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) { + ffsid, cancel := initFrostfsIFClientTest(t) + defer cancel() + + namespace := "namespace" + group := "group" + + tx := ffsid.cli.StartTx() + err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace)) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.CreateGroupCall(namespace, group)) + require.NoError(t, err) + ffsid.a.await(ffsid.cli.SendTx(tx)) + + // admin + + subjects := make([]testSubject, 5) + + for i := range subjects { + tx = ffsid.cli.StartTx() + subjects[i].key, subjects[i].addr = newKey(t) + err = tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey())) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(subjects[i].addr, "login"+strconv.Itoa(i))) + require.NoError(t, err) + err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(subjects[i].addr, namespace)) + require.NoError(t, err) + + if i > len(subjects)/2 { + err = tx.WrapCall(ffsid.cli.AddSubjectToGroupCall(subjects[i].addr, group)) + require.NoError(t, err) + } + ffsid.a.await(ffsid.cli.SendTx(tx)) + } + + nsSubjects, err := ffsid.cli.ListNamespaceSubjects(namespace) + require.NoError(t, err) + + res := make([]*client.SubjectExtended, len(nsSubjects)) + for i, subj := range nsSubjects { + res[i], err = ffsid.cli.GetSubjectExtended(subj) + require.NoError(t, err) + } + prettyPrintExtendedSubjects(res) +} + +func prettyPrintExtendedSubjects(subjects []*client.SubjectExtended) { + for _, subj := range subjects { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("login: %s, namespace: %v, groups: [ ", subj.Name, subj.Namespace)) + + for _, group := range subj.Groups { + sb.WriteString(group.Namespace + ":" + group.Name + " ") + } + + sb.WriteByte(']') + fmt.Println(sb.String()) + } +}