Files

462 lines
12 KiB
Go
Raw Permalink Normal View History

2025-10-03 14:45:52 -04:00
package keeper_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/suite"
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
didtypes "github.com/sonr-io/sonr/x/did/types"
"github.com/sonr-io/sonr/x/svc/keeper"
"github.com/sonr-io/sonr/x/svc/types"
)
// CapabilityTestSuite tests capability management
type CapabilityTestSuite struct {
suite.Suite
ctx context.Context
keeper keeper.Keeper
storeKey *storetypes.KVStoreKey
cdc codec.BinaryCodec
}
func (suite *CapabilityTestSuite) SetupTest() {
key := storetypes.NewKVStoreKey(types.StoreKey)
suite.storeKey = key
storeService := runtime.NewKVStoreService(key)
testCtx := testutil.DefaultContextWithDB(
suite.T(),
key,
storetypes.NewTransientStoreKey("transient_test"),
)
suite.ctx = testCtx.Ctx.WithBlockHeader(sdk.Context{}.BlockHeader())
encCfg := moduletestutil.MakeTestEncodingConfig()
suite.cdc = encCfg.Codec
authority := sdk.AccAddress([]byte("authority"))
// Mock DID keeper
mockDIDKeeper := &MockDIDKeeper{}
suite.keeper = keeper.NewKeeper(
suite.cdc,
storeService,
log.NewNopLogger(),
authority.String(),
mockDIDKeeper,
)
}
func TestCapabilityTestSuite(t *testing.T) {
suite.Run(t, new(CapabilityTestSuite))
}
// TestCreateCapability tests capability creation
func (suite *CapabilityTestSuite) TestCreateCapability() {
testCases := []struct {
name string
setup func() *types.ServiceCapability
expectError bool
errorMsg string
}{
{
name: "valid capability creation",
setup: func() *types.ServiceCapability {
return &types.ServiceCapability{
CapabilityId: "cap_service1_read_1234_0",
ServiceId: "service1",
Domain: "example.com",
Abilities: []string{"read", "write"},
Owner: "cosmos1abc123",
CreatedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
Revoked: false,
}
},
expectError: false,
},
{
name: "nil capability",
setup: func() *types.ServiceCapability {
return nil
},
expectError: true,
errorMsg: "capability cannot be nil",
},
{
name: "empty capability ID",
setup: func() *types.ServiceCapability {
return &types.ServiceCapability{
ServiceId: "service1",
Domain: "example.com",
Abilities: []string{"read"},
Owner: "cosmos1abc123",
}
},
expectError: true,
errorMsg: "capability ID cannot be empty",
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
capability := tc.setup()
err := suite.keeper.StoreCapability(suite.ctx, capability)
if tc.expectError {
suite.Require().Error(err)
suite.Require().Contains(err.Error(), tc.errorMsg)
} else {
suite.Require().NoError(err)
// Verify capability was stored
loaded, err := suite.keeper.LoadCapability(suite.ctx, capability.CapabilityId)
suite.Require().NoError(err)
suite.Require().Equal(capability.CapabilityId, loaded.CapabilityId)
suite.Require().Equal(capability.ServiceId, loaded.ServiceId)
suite.Require().Equal(capability.Abilities, loaded.Abilities)
}
})
}
}
// TestValidateCapability tests capability validation
func (suite *CapabilityTestSuite) TestValidateCapability() {
// Store a test capability
capability := &types.ServiceCapability{
CapabilityId: "cap_test_read_1234_0",
ServiceId: "test-service",
Domain: "test.com",
Abilities: []string{"read", "write"},
Owner: "cosmos1test",
CreatedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
Revoked: false,
}
err := suite.keeper.StoreCapability(suite.ctx, capability)
suite.Require().NoError(err)
testCases := []struct {
name string
capabilityID string
serviceID string
expectError bool
errorMsg string
}{
{
name: "valid capability",
capabilityID: "cap_test_read_1234_0",
serviceID: "test-service",
expectError: false,
},
{
name: "empty capability ID",
capabilityID: "",
serviceID: "test-service",
expectError: true,
errorMsg: "capability ID cannot be empty",
},
{
name: "empty service ID",
capabilityID: "cap_test_read_1234_0",
serviceID: "",
expectError: true,
errorMsg: "service ID cannot be empty",
},
{
name: "wrong service ID",
capabilityID: "cap_test_read_1234_0",
serviceID: "wrong-service",
expectError: true,
errorMsg: "does not belong to service",
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
validated, err := suite.keeper.ValidateCapability(
suite.ctx,
tc.capabilityID,
tc.serviceID,
)
if tc.expectError {
suite.Require().Error(err)
suite.Require().Contains(err.Error(), tc.errorMsg)
suite.Require().Nil(validated)
} else {
suite.Require().NoError(err)
suite.Require().NotNil(validated)
suite.Require().Equal(capability.CapabilityId, validated.CapabilityId)
}
})
}
}
// TestRevokeCapability tests capability revocation
func (suite *CapabilityTestSuite) TestRevokeCapability() {
// Store a test capability
capability := &types.ServiceCapability{
CapabilityId: "cap_revoke_test_1234_0",
ServiceId: "revoke-service",
Domain: "revoke.com",
Abilities: []string{"admin"},
Owner: "cosmos1owner",
CreatedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
Revoked: false,
}
err := suite.keeper.StoreCapability(suite.ctx, capability)
suite.Require().NoError(err)
testCases := []struct {
name string
capabilityID string
revoker string
expectError bool
errorMsg string
}{
{
name: "valid revocation by owner",
capabilityID: "cap_revoke_test_1234_0",
revoker: "cosmos1owner",
expectError: false,
},
{
name: "empty capability ID",
capabilityID: "",
revoker: "cosmos1owner",
expectError: true,
errorMsg: "capability ID cannot be empty",
},
{
name: "empty revoker",
capabilityID: "cap_revoke_test_1234_0",
revoker: "",
expectError: true,
errorMsg: "revoker cannot be empty",
},
{
name: "non-owner revocation",
capabilityID: "cap_revoke_test_1234_0",
revoker: "cosmos1other",
expectError: true,
errorMsg: "not authorized to revoke",
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// Reset capability state before tests that need it unrevoked
if tc.name == "valid revocation by owner" || tc.name == "non-owner revocation" {
capability.Revoked = false
err := suite.keeper.StoreCapability(suite.ctx, capability)
suite.Require().NoError(err)
}
err := suite.keeper.RevokeCapability(suite.ctx, tc.capabilityID, tc.revoker)
if tc.expectError {
suite.Require().Error(err)
suite.Require().Contains(err.Error(), tc.errorMsg)
} else {
suite.Require().NoError(err)
// Verify capability is revoked
loaded, err := suite.keeper.LoadCapability(suite.ctx, tc.capabilityID)
suite.Require().NoError(err)
suite.Require().True(loaded.Revoked)
}
})
}
}
// TestExpiredCapability tests expired capability validation
func (suite *CapabilityTestSuite) TestExpiredCapability() {
// Store an expired capability
expiredCapability := &types.ServiceCapability{
CapabilityId: "cap_expired_test_1234_0",
ServiceId: "expired-service",
Domain: "expired.com",
Abilities: []string{"read"},
Owner: "cosmos1expired",
CreatedAt: time.Now().Add(-48 * time.Hour).Unix(),
ExpiresAt: time.Now().Add(-24 * time.Hour).Unix(), // Expired
Revoked: false,
}
err := suite.keeper.StoreCapability(suite.ctx, expiredCapability)
suite.Require().NoError(err)
// Validation should fail for expired capability
validated, err := suite.keeper.ValidateCapability(
suite.ctx,
"cap_expired_test_1234_0",
"expired-service",
)
suite.Require().Error(err)
suite.Require().Contains(err.Error(), "has expired")
suite.Require().Nil(validated)
}
// TestCapabilityChainValidation tests permission chain validation
func (suite *CapabilityTestSuite) TestCapabilityChainValidation() {
// Store multiple capabilities for chain validation
capabilities := []*types.ServiceCapability{
{
CapabilityId: "cap_chain_read_1234_0",
ServiceId: "chain-service",
Domain: "chain.com",
Abilities: []string{"read"},
Owner: "cosmos1chain",
CreatedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
Revoked: false,
},
{
CapabilityId: "cap_chain_write_1234_1",
ServiceId: "chain-service",
Domain: "chain.com",
Abilities: []string{"write"},
Owner: "cosmos1chain",
CreatedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
Revoked: false,
},
}
for _, cap := range capabilities {
err := suite.keeper.StoreCapability(suite.ctx, cap)
suite.Require().NoError(err)
}
testCases := []struct {
name string
capabilityChain []string
serviceID string
requiredPermissions []string
expectError bool
errorMsg string
}{
{
name: "valid chain with all permissions",
capabilityChain: []string{"cap_chain_read_1234_0", "cap_chain_write_1234_1"},
serviceID: "chain-service",
requiredPermissions: []string{"read", "write"},
expectError: false,
},
{
name: "empty capability chain",
capabilityChain: []string{},
serviceID: "chain-service",
requiredPermissions: []string{"read"},
expectError: true,
errorMsg: "capability chain cannot be empty",
},
{
name: "empty service ID",
capabilityChain: []string{"cap_chain_read_1234_0"},
serviceID: "",
requiredPermissions: []string{"read"},
expectError: true,
errorMsg: "service ID cannot be empty",
},
{
name: "insufficient capabilities",
capabilityChain: []string{"cap_chain_read_1234_0"},
serviceID: "chain-service",
requiredPermissions: []string{"read", "write", "admin"},
expectError: true,
errorMsg: "insufficient capabilities",
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
err := suite.keeper.ValidatePermissionCapabilityChain(
suite.ctx,
tc.capabilityChain,
tc.serviceID,
tc.requiredPermissions,
)
if tc.expectError {
suite.Require().Error(err)
if tc.errorMsg != "" {
suite.Require().Contains(err.Error(), tc.errorMsg)
}
} else {
suite.Require().NoError(err)
}
})
}
}
// MockDIDKeeper is a mock implementation of DIDKeeper for testing
type MockDIDKeeper struct{}
func (m *MockDIDKeeper) ResolveDID(
ctx context.Context,
did string,
) (*didtypes.DIDDocument, *didtypes.DIDDocumentMetadata, error) {
doc := &didtypes.DIDDocument{
Id: did,
PrimaryController: did,
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: did + "#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
Controller: did,
},
},
Deactivated: false,
}
metadata := &didtypes.DIDDocumentMetadata{
VersionId: "1",
Created: time.Now().Unix(),
Updated: time.Now().Unix(),
Deactivated: 0,
}
return doc, metadata, nil
}
func (m *MockDIDKeeper) GetDIDDocument(
ctx context.Context,
did string,
) (*didtypes.DIDDocument, error) {
return &didtypes.DIDDocument{
Id: did,
PrimaryController: did,
VerificationMethod: []*didtypes.VerificationMethod{
{
Id: did + "#key1",
VerificationMethodKind: "Ed25519VerificationKey2020",
Controller: did,
},
},
Deactivated: false,
}, nil
}
func (m *MockDIDKeeper) VerifyDIDDocumentSignature(
ctx context.Context,
did string,
signature []byte,
) (bool, error) {
// For testing, always return true
return true, nil
}