package container

import (
	"context"
	"crypto/ecdsa"
	"encoding/hex"
	"errors"
	"fmt"
	"testing"

	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
	session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
	"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
	containercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
	frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
	cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
	containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
	sessiontest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session/test"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
	commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
	nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/stretchr/testify/require"
)

const (
	testDomainName = "testdomainname"
	testDomainZone = "testdomainname.ns"
)

func TestAPE(t *testing.T) {
	t.Parallel()
	t.Run("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
	t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
	t.Run("deny get container for others", testDenyGetContainerForOthers)
	t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag)
	t.Run("deny get container by group id", testDenyGetContainerByGroupID)
	t.Run("deny set container eACL for IR", testDenySetContainerEACLForIR)
	t.Run("deny get container eACL for IR with session token", testDenyGetContainerEACLForIRSessionToken)
	t.Run("deny put container for others with session token", testDenyPutContainerForOthersSessionToken)
	t.Run("deny put container, read namespace from frostfsID", testDenyPutContainerReadNamespaceFromFrostfsID)
	t.Run("deny put container with invlaid namespace", testDenyPutContainerInvalidNamespace)
	t.Run("deny list containers for owner with PK", testDenyListContainersForPK)
	t.Run("deny list containers by namespace invalidation", testDenyListContainersValidationNamespaceError)
}

func testAllowThenDenyGetContainerRuleDefined(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	addDefaultAllowGetPolicy(t, router, contID)

	req := &container.GetRequest{}
	req.SetBody(&container.GetRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	_, err = apeSrv.Get(context.Background(), req)
	require.NoError(t, err)

	_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodGetContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
					},
				},
			},
		},
	})
	require.NoError(t, err)

	resp, err := apeSrv.Get(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
	require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
}

func testDenyGetContainerNoRuleFound(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	req := &container.GetRequest{}
	req.SetBody(&container.GetRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	resp, err := apeSrv.Get(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
	require.Contains(t, errAccessDenied.Reason(), chain.NoRuleFound.String())
}

func testDenyGetContainerForOthers(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodGetContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorRole,
						Value:  nativeschema.PropertyValueContainerRoleOthers,
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := &container.GetRequest{}
	req.SetBody(&container.GetRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	resp, err := apeSrv.Get(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyGetContainerByUserClaimTag(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)

	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{
			pk.PublicKey().GetScriptHash(): {
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
			},
		},
		subjectsExt: map[util.Uint160]*client.SubjectExtended{
			pk.PublicKey().GetScriptHash(): {
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
				Groups: []*client.Group{
					{
						ID: 19888,
					},
				},
			},
		},
	}

	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodGetContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"),
						Value:  "value100",
						Op:     chain.CondStringNotEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := &container.GetRequest{}
	req.SetBody(&container.GetRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	resp, err := apeSrv.Get(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyGetContainerByGroupID(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)

	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{
			pk.PublicKey().GetScriptHash(): {
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
			},
		},
		subjectsExt: map[util.Uint160]*client.SubjectExtended{
			pk.PublicKey().GetScriptHash(): {
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
				Groups: []*client.Group{
					{
						ID: 19888,
					},
				},
			},
		},
	}

	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodGetContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    commonschema.PropertyKeyFrostFSIDGroupID,
						Value:  "19888",
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := &container.GetRequest{}
	req.SetBody(&container.GetRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	resp, err := apeSrv.Get(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenySetContainerEACLForIR(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodSetContainerEACL,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorRole,
						Value:  nativeschema.PropertyValueContainerRoleIR,
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := &container.SetExtendedACLRequest{}
	req.SetBody(&container.SetExtendedACLRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetEACL(&acl.Table{})
	req.GetBody().GetEACL().SetContainerID(&refContID)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
	ir.keys = append(ir.keys, pk.PublicKey().Bytes())

	resp, err := apeSrv.SetExtendedACL(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyGetContainerEACLForIRSessionToken(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	contRdr.c[contID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(nm.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	nm.netmaps[nm.currentEpoch] = &testNetmap
	nm.netmaps[nm.currentEpoch-1] = &testNetmap

	_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodGetContainerEACL,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorRole,
						Value:  nativeschema.PropertyValueContainerRoleIR,
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := &container.GetExtendedACLRequest{}
	req.SetBody(&container.GetExtendedACLRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	sessionPK, err := keys.NewPrivateKey()
	require.NoError(t, err)
	sToken := sessiontest.ContainerSigned()
	sToken.ApplyOnlyTo(contID)
	require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
	var sTokenV2 session.Token
	sToken.WriteToV2(&sTokenV2)
	metaHeader := new(session.RequestMetaHeader)
	metaHeader.SetSessionToken(&sTokenV2)
	req.SetMetaHeader(metaHeader)

	ir.keys = append(ir.keys, sessionPK.PublicKey().Bytes())

	resp, err := apeSrv.GetExtendedACL(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyPutContainerForOthersSessionToken(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	testContainer := containertest.Container()

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}

	_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodPutContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						nativeschema.ResourceFormatRootContainers,
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorRole,
						Value:  nativeschema.PropertyValueContainerRoleOthers,
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := initPutRequest(t, testContainer)

	resp, err := apeSrv.Put(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyPutContainerReadNamespaceFromFrostfsID(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}

	cnrID, testContainer := initTestContainer(t, true)
	contRdr.c[cnrID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}

	_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodPutContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorRole,
						Value:  nativeschema.PropertyValueContainerRoleOthers,
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := initPutRequest(t, testContainer)
	ownerScriptHash := initOwnerIDScriptHash(t, testContainer)

	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{
			ownerScriptHash: {
				Namespace: testDomainName,
				Name:      testDomainName,
			},
		},
		subjectsExt: map[util.Uint160]*client.SubjectExtended{
			ownerScriptHash: {
				Namespace: testDomainName,
				Name:      testDomainName,
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
				Groups: []*client.Group{
					{
						ID: 19888,
					},
				},
			},
		},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
	resp, err := apeSrv.Put(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyPutContainerInvalidNamespace(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}

	cnrID, testContainer := initTestContainer(t, false)
	var domain cnrSDK.Domain
	domain.SetName("incorrect" + testDomainName)
	domain.SetZone("incorrect" + testDomainZone)
	cnrSDK.WriteDomain(&testContainer, domain)
	contRdr.c[cnrID] = &containercore.Container{Value: testContainer}

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}

	_, _, err := router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodPutContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorRole,
						Value:  nativeschema.PropertyValueContainerRoleOthers,
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := initPutRequest(t, testContainer)
	ownerScriptHash := initOwnerIDScriptHash(t, testContainer)

	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{
			ownerScriptHash: {
				Namespace: testDomainName,
				Name:      testDomainName,
			},
		},
		subjectsExt: map[util.Uint160]*client.SubjectExtended{
			ownerScriptHash: {
				Namespace: testDomainName,
				Name:      testDomainName,
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
				Groups: []*client.Group{
					{
						ID: 19888,
					},
				},
			},
		},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
	resp, err := apeSrv.Put(context.Background(), req)
	require.Nil(t, resp)
	require.ErrorContains(t, err, "invalid domain zone")
}

func testDenyListContainersForPK(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}
	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{},
	}
	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)

	_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodListContainers,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						nativeschema.ResourceFormatRootContainers,
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorPublicKey,
						Value:  hex.EncodeToString(pk.PublicKey().Bytes()),
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	var userID user.ID
	user.IDFromKey(&userID, pk.PrivateKey.PublicKey)

	req := &container.ListRequest{}
	req.SetBody(&container.ListRequestBody{})
	var ownerID refs.OwnerID
	userID.WriteToV2(&ownerID)
	req.GetBody().SetOwnerID(&ownerID)

	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	resp, err := apeSrv.List(context.Background(), req)
	require.Nil(t, resp)
	var errAccessDenied *apistatus.ObjectAccessDenied
	require.ErrorAs(t, err, &errAccessDenied)
}

func testDenyListContainersValidationNamespaceError(t *testing.T) {
	t.Parallel()
	srv := &srvStub{
		calls: map[string]int{},
	}
	router := inmemory.NewInMemory()
	contRdr := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}
	ir := &irStub{
		keys: [][]byte{},
	}
	nm := &netmapStub{}

	actorPK, err := keys.NewPrivateKey()
	require.NoError(t, err)

	ownerPK, err := keys.NewPrivateKey()
	require.NoError(t, err)

	actorScriptHash, ownerScriptHash := initActorOwnerScriptHashes(t, actorPK, ownerPK)

	const actorDomain = "actor" + testDomainName

	frostfsIDSubjectReader := &frostfsidStub{
		subjects: map[util.Uint160]*client.Subject{
			actorScriptHash: {
				Namespace: actorDomain,
				Name:      actorDomain,
			},
			ownerScriptHash: {
				Namespace: testDomainName,
				Name:      testDomainName,
			},
		},
		subjectsExt: map[util.Uint160]*client.SubjectExtended{
			actorScriptHash: {
				Namespace: actorDomain,
				Name:      actorDomain,
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
				Groups: []*client.Group{
					{
						ID: 19777,
					},
				},
			},
			ownerScriptHash: {
				Namespace: testDomainName,
				Name:      testDomainName,
				KV: map[string]string{
					"tag-attr1": "value1",
					"tag-attr2": "value2",
				},
				Groups: []*client.Group{
					{
						ID: 19888,
					},
				},
			},
		},
	}

	apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)

	nm.currentEpoch = 100
	nm.netmaps = map[uint64]*netmap.NetMap{}

	_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(""), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.AccessDenied,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodListContainers,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						nativeschema.ResourceFormatRootContainers,
					},
				},
				Condition: []chain.Condition{
					{
						Object: chain.ObjectRequest,
						Key:    nativeschema.PropertyKeyActorPublicKey,
						Value:  actorPK.PublicKey().String(),
						Op:     chain.CondStringEquals,
					},
				},
			},
		},
	})
	require.NoError(t, err)

	req := initListRequest(t, actorPK, ownerPK)

	resp, err := apeSrv.List(context.Background(), req)
	require.Nil(t, resp)
	require.ErrorContains(t, err, "actor namespace "+actorDomain+" differs")
}

type srvStub struct {
	calls map[string]int
}

func (s *srvStub) AnnounceUsedSpace(context.Context, *container.AnnounceUsedSpaceRequest) (*container.AnnounceUsedSpaceResponse, error) {
	s.calls["AnnounceUsedSpace"]++
	return &container.AnnounceUsedSpaceResponse{}, nil
}

func (s *srvStub) Delete(context.Context, *container.DeleteRequest) (*container.DeleteResponse, error) {
	s.calls["Delete"]++
	return &container.DeleteResponse{}, nil
}

func (s *srvStub) Get(context.Context, *container.GetRequest) (*container.GetResponse, error) {
	s.calls["Get"]++
	return &container.GetResponse{}, nil
}

func (s *srvStub) GetExtendedACL(context.Context, *container.GetExtendedACLRequest) (*container.GetExtendedACLResponse, error) {
	s.calls["GetExtendedACL"]++
	return &container.GetExtendedACLResponse{}, nil
}

func (s *srvStub) List(context.Context, *container.ListRequest) (*container.ListResponse, error) {
	s.calls["List"]++
	return &container.ListResponse{}, nil
}

func (s *srvStub) Put(context.Context, *container.PutRequest) (*container.PutResponse, error) {
	s.calls["Put"]++
	return &container.PutResponse{}, nil
}

func (s *srvStub) SetExtendedACL(context.Context, *container.SetExtendedACLRequest) (*container.SetExtendedACLResponse, error) {
	s.calls["SetExtendedACL"]++
	return &container.SetExtendedACLResponse{}, nil
}

type irStub struct {
	keys [][]byte
}

func (s *irStub) InnerRingKeys() ([][]byte, error) {
	return s.keys, nil
}

type containerStub struct {
	c map[cid.ID]*containercore.Container
}

func (s *containerStub) Get(id cid.ID) (*containercore.Container, error) {
	if v, ok := s.c[id]; ok {
		return v, nil
	}
	return nil, errors.New("container not found")
}

type netmapStub struct {
	netmaps      map[uint64]*netmap.NetMap
	currentEpoch uint64
}

func (s *netmapStub) GetNetMap(diff uint64) (*netmap.NetMap, error) {
	if diff >= s.currentEpoch {
		return nil, errors.New("invalid diff")
	}
	return s.GetNetMapByEpoch(s.currentEpoch - diff)
}

func (s *netmapStub) GetNetMapByEpoch(epoch uint64) (*netmap.NetMap, error) {
	if nm, found := s.netmaps[epoch]; found {
		return nm, nil
	}
	return nil, errors.New("netmap not found")
}

func (s *netmapStub) Epoch() (uint64, error) {
	return s.currentEpoch, nil
}

type frostfsidStub struct {
	subjects    map[util.Uint160]*client.Subject
	subjectsExt map[util.Uint160]*client.SubjectExtended
}

func (f *frostfsidStub) GetSubject(owner util.Uint160) (*client.Subject, error) {
	s, ok := f.subjects[owner]
	if !ok {
		return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
	}
	return s, nil
}

func (f *frostfsidStub) GetSubjectExtended(owner util.Uint160) (*client.SubjectExtended, error) {
	s, ok := f.subjectsExt[owner]
	if !ok {
		return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage)
	}
	return s, nil
}

type testAPEServer struct {
	engine engine.Engine

	containerReader *containerStub

	ir *irStub

	netmap *netmapStub

	frostfsIDSubjectReader *frostfsidStub

	apeChecker *apeChecker
}

func newTestAPEServer() testAPEServer {
	srv := &srvStub{
		calls: map[string]int{},
	}

	engine := inmemory.NewInMemory()

	containerReader := &containerStub{
		c: map[cid.ID]*containercore.Container{},
	}

	ir := &irStub{
		keys: [][]byte{},
	}

	netmap := &netmapStub{}

	frostfsIDSubjectReader := &frostfsidStub{
		subjects:    map[util.Uint160]*client.Subject{},
		subjectsExt: map[util.Uint160]*client.SubjectExtended{},
	}

	apeChecker := &apeChecker{
		router:          engine,
		reader:          containerReader,
		ir:              ir,
		nm:              netmap,
		frostFSIDClient: frostfsIDSubjectReader,
		next:            srv,
	}

	return testAPEServer{
		engine:                 engine,
		containerReader:        containerReader,
		ir:                     ir,
		netmap:                 netmap,
		frostfsIDSubjectReader: frostfsIDSubjectReader,
		apeChecker:             apeChecker,
	}
}

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

	t.Run("check root-defined container in root-defined container target rule", func(t *testing.T) {
		t.Parallel()

		components := newTestAPEServer()
		contID, testContainer := initTestContainer(t, false)
		components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
		initTestNetmap(components.netmap)

		_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
			Rules: []chain.Rule{
				{
					Status: chain.AccessDenied,
					Actions: chain.Actions{
						Names: []string{
							nativeschema.MethodGetContainer,
						},
					},
					Resources: chain.Resources{
						Names: []string{
							fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
						},
					},
					Condition: []chain.Condition{
						{
							Object: chain.ObjectRequest,
							Key:    nativeschema.PropertyKeyActorRole,
							Value:  nativeschema.PropertyValueContainerRoleOthers,
							Op:     chain.CondStringEquals,
						},
					},
				},
			},
		})
		require.NoError(t, err)

		req := initTestGetContainerRequest(t, contID)

		err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
		aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
		require.ErrorContains(t, err, aErr.Error())
	})

	t.Run("check root-defined container in testdomain-defined container target rule", func(t *testing.T) {
		t.Parallel()

		components := newTestAPEServer()
		contID, testContainer := initTestContainer(t, false)
		components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
		initTestNetmap(components.netmap)

		_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
			Rules: []chain.Rule{
				{
					Status: chain.AccessDenied,
					Actions: chain.Actions{
						Names: []string{
							nativeschema.MethodGetContainer,
						},
					},
					Resources: chain.Resources{
						Names: []string{
							fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
						},
					},
					Condition: []chain.Condition{
						{
							Object: chain.ObjectRequest,
							Key:    nativeschema.PropertyKeyActorRole,
							Value:  nativeschema.PropertyValueContainerRoleOthers,
							Op:     chain.CondStringEquals,
						},
					},
				},
			},
		})
		require.NoError(t, err)

		addDefaultAllowGetPolicy(t, components.engine, contID)

		req := initTestGetContainerRequest(t, contID)

		err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
		require.NoError(t, err)
	})

	t.Run("check root-defined container in testdomain namespace target rule", func(t *testing.T) {
		t.Parallel()

		components := newTestAPEServer()
		contID, testContainer := initTestContainer(t, false)
		components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
		initTestNetmap(components.netmap)

		_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
			Rules: []chain.Rule{
				{
					Status: chain.AccessDenied,
					Actions: chain.Actions{
						Names: []string{
							nativeschema.MethodGetContainer,
						},
					},
					Resources: chain.Resources{
						Names: []string{
							fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
						},
					},
					Condition: []chain.Condition{
						{
							Object: chain.ObjectRequest,
							Key:    nativeschema.PropertyKeyActorRole,
							Value:  nativeschema.PropertyValueContainerRoleOthers,
							Op:     chain.CondStringEquals,
						},
					},
				},
			},
		})
		require.NoError(t, err)

		addDefaultAllowGetPolicy(t, components.engine, contID)

		req := initTestGetContainerRequest(t, contID)

		err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
		require.NoError(t, err)
	})

	t.Run("check testdomain-defined container in root-defined container target rule", func(t *testing.T) {
		t.Parallel()

		components := newTestAPEServer()
		contID, testContainer := initTestContainer(t, true)
		components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
		initTestNetmap(components.netmap)

		_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
			Rules: []chain.Rule{
				{
					Status: chain.AccessDenied,
					Actions: chain.Actions{
						Names: []string{
							nativeschema.MethodGetContainer,
						},
					},
					Resources: chain.Resources{
						Names: []string{
							fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
						},
					},
					Condition: []chain.Condition{
						{
							Object: chain.ObjectRequest,
							Key:    nativeschema.PropertyKeyActorRole,
							Value:  nativeschema.PropertyValueContainerRoleOthers,
							Op:     chain.CondStringEquals,
						},
					},
				},
			},
		})
		require.NoError(t, err)

		addDefaultAllowGetPolicy(t, components.engine, contID)

		req := initTestGetContainerRequest(t, contID)

		err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
		require.NoError(t, err)
	})

	t.Run("check testdomain-defined container in testdomain-defined container target rule", func(t *testing.T) {
		t.Parallel()

		components := newTestAPEServer()
		contID, testContainer := initTestContainer(t, true)
		components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
		initTestNetmap(components.netmap)

		_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
			Rules: []chain.Rule{
				{
					Status: chain.AccessDenied,
					Actions: chain.Actions{
						Names: []string{
							nativeschema.MethodGetContainer,
						},
					},
					Resources: chain.Resources{
						Names: []string{
							fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainer, testDomainName, contID.EncodeToString()),
						},
					},
					Condition: []chain.Condition{
						{
							Object: chain.ObjectRequest,
							Key:    nativeschema.PropertyKeyActorRole,
							Value:  nativeschema.PropertyValueContainerRoleOthers,
							Op:     chain.CondStringEquals,
						},
					},
				},
			},
		})
		require.NoError(t, err)

		addDefaultAllowGetPolicy(t, components.engine, contID)

		req := initTestGetContainerRequest(t, contID)

		err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
		aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
		require.ErrorContains(t, err, aErr.Error())
	})

	t.Run("check testdomain-defined container in testdomain namespace target rule", func(t *testing.T) {
		t.Parallel()

		components := newTestAPEServer()
		contID, testContainer := initTestContainer(t, true)
		components.containerReader.c[contID] = &containercore.Container{Value: testContainer}
		initTestNetmap(components.netmap)

		_, _, err := components.engine.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(testDomainName), &chain.Chain{
			Rules: []chain.Rule{
				{
					Status: chain.AccessDenied,
					Actions: chain.Actions{
						Names: []string{
							nativeschema.MethodGetContainer,
						},
					},
					Resources: chain.Resources{
						Names: []string{
							fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainers, testDomainName),
						},
					},
					Condition: []chain.Condition{
						{
							Object: chain.ObjectRequest,
							Key:    nativeschema.PropertyKeyActorRole,
							Value:  nativeschema.PropertyValueContainerRoleOthers,
							Op:     chain.CondStringEquals,
						},
					},
				},
			},
		})
		require.NoError(t, err)

		req := initTestGetContainerRequest(t, contID)

		err = components.apeChecker.validateContainerBoundedOperation(req.GetBody().GetContainerID(), req.GetMetaHeader(), req.GetVerificationHeader(), nativeschema.MethodGetContainer)
		aErr := apeErr(nativeschema.MethodGetContainer, chain.AccessDenied)
		require.ErrorContains(t, err, aErr.Error())
	})
}

func initTestGetContainerRequest(t *testing.T, contID cid.ID) *container.GetRequest {
	req := &container.GetRequest{}
	req.SetBody(&container.GetRequestBody{})
	var refContID refs.ContainerID
	contID.WriteToV2(&refContID)
	req.GetBody().SetContainerID(&refContID)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
	return req
}

func initTestNetmap(netmapStub *netmapStub) {
	netmapStub.currentEpoch = 100
	netmapStub.netmaps = map[uint64]*netmap.NetMap{}
	var testNetmap netmap.NetMap
	testNetmap.SetEpoch(netmapStub.currentEpoch)
	testNetmap.SetNodes([]netmap.NodeInfo{{}})
	netmapStub.netmaps[netmapStub.currentEpoch] = &testNetmap
	netmapStub.netmaps[netmapStub.currentEpoch-1] = &testNetmap
}

func initTestContainer(t *testing.T, isDomainSet bool) (cid.ID, cnrSDK.Container) {
	contID := cidtest.ID()
	testContainer := containertest.Container()
	pp := netmap.PlacementPolicy{}
	require.NoError(t, pp.DecodeString("REP 1"))
	testContainer.SetPlacementPolicy(pp)
	if isDomainSet {
		// no domain defined -> container is defined in root namespace
		var domain cnrSDK.Domain
		domain.SetName(testDomainName)
		domain.SetZone(testDomainZone)
		cnrSDK.WriteDomain(&testContainer, domain)
	}
	return contID, testContainer
}

func initPutRequest(t *testing.T, testContainer cnrSDK.Container) *container.PutRequest {
	req := &container.PutRequest{}
	req.SetBody(&container.PutRequestBody{})
	var reqCont container.Container
	testContainer.WriteToV2(&reqCont)
	req.GetBody().SetContainer(&reqCont)

	sessionPK, err := keys.NewPrivateKey()
	require.NoError(t, err)
	sToken := sessiontest.ContainerSigned()
	sToken.ApplyOnlyTo(cid.ID{})
	require.NoError(t, sToken.Sign(sessionPK.PrivateKey))
	var sTokenV2 session.Token
	sToken.WriteToV2(&sTokenV2)
	metaHeader := new(session.RequestMetaHeader)
	metaHeader.SetSessionToken(&sTokenV2)
	req.SetMetaHeader(metaHeader)

	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)
	require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))

	return req
}

func initOwnerIDScriptHash(t *testing.T, testContainer cnrSDK.Container) util.Uint160 {
	var ownerSDK *user.ID
	owner := testContainer.Owner()
	ownerSDK = &owner
	sc, err := ownerSDK.ScriptHash()
	require.NoError(t, err)
	return sc
}

func initActorOwnerScriptHashes(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) (actorScriptHash util.Uint160, ownerScriptHash util.Uint160) {
	var actorUserID user.ID
	user.IDFromKey(&actorUserID, ecdsa.PublicKey(*actorPK.PublicKey()))
	var err error
	actorScriptHash, err = actorUserID.ScriptHash()
	require.NoError(t, err)

	var ownerUserID user.ID
	user.IDFromKey(&ownerUserID, ecdsa.PublicKey(*ownerPK.PublicKey()))
	ownerScriptHash, err = ownerUserID.ScriptHash()
	require.NoError(t, err)
	require.NotEqual(t, ownerScriptHash.String(), actorScriptHash.String())
	return
}

func initListRequest(t *testing.T, actorPK *keys.PrivateKey, ownerPK *keys.PrivateKey) *container.ListRequest {
	var ownerUserID user.ID
	user.IDFromKey(&ownerUserID, ownerPK.PrivateKey.PublicKey)

	req := &container.ListRequest{}
	req.SetBody(&container.ListRequestBody{})
	var ownerID refs.OwnerID
	ownerUserID.WriteToV2(&ownerID)
	req.GetBody().SetOwnerID(&ownerID)

	require.NoError(t, signature.SignServiceMessage(&actorPK.PrivateKey, req))
	return req
}

func addDefaultAllowGetPolicy(t *testing.T, e engine.Engine, contID cid.ID) {
	_, _, err := e.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(contID.EncodeToString()), &chain.Chain{
		Rules: []chain.Rule{
			{
				Status: chain.Allow,
				Actions: chain.Actions{
					Names: []string{
						nativeschema.MethodGetContainer,
					},
				},
				Resources: chain.Resources{
					Names: []string{
						nativeschema.ResourceFormatAllContainers,
					},
				},
			},
		},
	})
	require.NoError(t, err)
}