Skip to content

Commit 7877494

Browse files
committed
feat: add integration tests for role management functionality
- Introduced a new integration test for verifying the granting and revoking of the `system:network_writer` role. - Enhanced the ServerFixture to manage private keys for the manager and database owner. - Updated utility functions to improve transaction handling and assertions. - Refactored existing test code for better type safety and clarity.
1 parent f24ceab commit 7877494

File tree

3 files changed

+209
-110
lines changed

3 files changed

+209
-110
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/kwilteam/kwil-db/core/crypto"
8+
"github.com/kwilteam/kwil-db/core/crypto/auth"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"github.com/trufnetwork/sdk-go/core/tnclient"
12+
apitypes "github.com/trufnetwork/sdk-go/core/types"
13+
"github.com/trufnetwork/sdk-go/core/util"
14+
)
15+
16+
// TestRoleManagement verifies granting and revoking the `system:network_writer` role.
17+
func TestRoleManagement(t *testing.T) {
18+
fixture := NewServerFixture(t)
19+
err := fixture.Setup()
20+
t.Cleanup(func() {
21+
fixture.Teardown()
22+
})
23+
require.NoError(t, err, "Failed to setup server fixture")
24+
25+
ctx := context.Background()
26+
27+
// ---------------------------------------------------------------------
28+
// Bootstrap: manager client (already member of system:network_writers_manager)
29+
// ---------------------------------------------------------------------
30+
managerClient, err := tnclient.NewClient(ctx, TestKwilProvider, tnclient.WithSigner(&auth.EthPersonalSigner{Key: *fixture.ManagerPrivateKey}))
31+
require.NoError(t, err, "failed to create manager client")
32+
33+
managerAddr := managerClient.Address()
34+
35+
// ---------------------------------------------------------------------
36+
// newWriter client – will be granted / revoked
37+
// ---------------------------------------------------------------------
38+
newWriterPk, err := crypto.Secp256k1PrivateKeyFromHex("2222222222222222222222222222222222222222222222222222222222222222")
39+
require.NoError(t, err)
40+
newWriterClient, err := tnclient.NewClient(ctx, TestKwilProvider, tnclient.WithSigner(&auth.EthPersonalSigner{Key: *newWriterPk}))
41+
require.NoError(t, err)
42+
newWriterAddr := newWriterClient.Address()
43+
44+
// randomUser client – never gets the role
45+
randomPk, err := crypto.Secp256k1PrivateKeyFromHex("3333333333333333333333333333333333333333333333333333333333333333")
46+
require.NoError(t, err)
47+
randomClient, err := tnclient.NewClient(ctx, TestKwilProvider, tnclient.WithSigner(&auth.EthPersonalSigner{Key: *randomPk}))
48+
require.NoError(t, err)
49+
randomAddr := randomClient.Address()
50+
51+
// ---------------------------------------------------------------------
52+
// Load Role Management API
53+
// ---------------------------------------------------------------------
54+
roleMgmt, err := managerClient.LoadRoleManagementActions()
55+
require.NoError(t, err)
56+
57+
// Helper to check membership of a single wallet
58+
isWriter := func(wallet util.EthereumAddress) bool {
59+
res, err := roleMgmt.AreMembersOf(ctx, apitypes.AreMembersOfInput{
60+
Owner: "system",
61+
RoleName: "network_writer",
62+
Wallets: []util.EthereumAddress{wallet},
63+
})
64+
require.NoError(t, err)
65+
require.Len(t, res, 1)
66+
return res[0].IsMember
67+
}
68+
69+
// Initial assertions – none of the three wallets are writers.
70+
assert.False(t, isWriter(managerAddr))
71+
assert.False(t, isWriter(newWriterAddr))
72+
assert.False(t, isWriter(randomAddr))
73+
74+
//---------------------------------------------------------------------
75+
// Grant role to newWriter
76+
//---------------------------------------------------------------------
77+
grantTx, err := roleMgmt.GrantRole(ctx, apitypes.GrantRoleInput{
78+
Owner: "system",
79+
RoleName: "network_writer",
80+
Wallets: []util.EthereumAddress{newWriterAddr},
81+
})
82+
require.NoError(t, err)
83+
waitTxToBeMinedWithSuccess(t, ctx, managerClient, grantTx)
84+
85+
assert.True(t, isWriter(newWriterAddr))
86+
87+
//---------------------------------------------------------------------
88+
// newWriter deploys a primitive stream – should succeed
89+
//---------------------------------------------------------------------
90+
streamID := util.GenerateStreamId("role-management-go-test")
91+
txHash, err := newWriterClient.DeployStream(ctx, streamID, apitypes.StreamTypePrimitive)
92+
require.NoError(t, err, "new writer failed to deploy primitive stream")
93+
waitTxToBeMinedWithSuccess(t, ctx, newWriterClient, txHash)
94+
95+
// Clean up the stream after test
96+
t.Cleanup(func() {
97+
destroyHash, err := newWriterClient.DestroyStream(ctx, streamID)
98+
if err == nil {
99+
waitTxToBeMinedWithSuccess(t, ctx, newWriterClient, destroyHash)
100+
}
101+
})
102+
103+
//---------------------------------------------------------------------
104+
// Revoke role from newWriter
105+
//---------------------------------------------------------------------
106+
revokeTx, err := roleMgmt.RevokeRole(ctx, apitypes.RevokeRoleInput{
107+
Owner: "system",
108+
RoleName: "network_writer",
109+
Wallets: []util.EthereumAddress{newWriterAddr},
110+
})
111+
require.NoError(t, err)
112+
waitTxToBeMinedWithSuccess(t, ctx, managerClient, revokeTx)
113+
114+
assert.False(t, isWriter(newWriterAddr))
115+
116+
// Deployment should now fail on-chain for newWriter (submission succeeds, tx fails)
117+
streamIDRevoked := util.GenerateStreamId("role-management-go-test-revoked")
118+
txRevoked, err := newWriterClient.DeployStream(ctx, streamIDRevoked, apitypes.StreamTypePrimitive)
119+
// client-side submission should succeed; failure occurs on-chain
120+
require.NoError(t, err, "tx submission should succeed even though on-chain will fail")
121+
waitTxToBeMinedWithFailure(t, ctx, newWriterClient, txRevoked)
122+
123+
// randomUser should also fail on-chain
124+
randStream := util.GenerateStreamId("role-management-go-test-random")
125+
txRand, err := randomClient.DeployStream(ctx, randStream, apitypes.StreamTypePrimitive)
126+
require.NoError(t, err, "tx submission should succeed for random user (failure is on-chain)")
127+
waitTxToBeMinedWithFailure(t, ctx, randomClient, txRand)
128+
}

tests/integration/server_fixture.go

Lines changed: 32 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,29 @@ import (
1111
"testing"
1212
"time"
1313

14-
"github.com/ethereum/go-ethereum/crypto"
1514
"github.com/joho/godotenv"
1615
kwilcrypto "github.com/kwilteam/kwil-db/core/crypto"
1716
"github.com/kwilteam/kwil-db/core/crypto/auth"
18-
"github.com/trufnetwork/sdk-go/core/tnclient"
19-
"github.com/trufnetwork/sdk-go/core/util"
2017
)
2118

2219
// Constants for the test setup
2320
const (
24-
networkName = "truf-test-network"
25-
TestKwilProvider = "http://localhost:8484"
26-
TestPrivateKey = "1111111111111111111111111111111111111111111111111111111111111111" // Test private key
27-
DB_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000001"
28-
DB_PUBLIC_KEY = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"
21+
networkName = "truf-test-network"
22+
TestKwilProvider = "http://localhost:8484"
23+
managerPrivateKey = "1111111111111111111111111111111111111111111111111111111111111111" // manager wallet for system roles
24+
DB_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000001" // database owner wallet
25+
DB_PUBLIC_KEY = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"
2926
)
3027

3128
// ServerFixture is a fixture for setting up and tearing down a Trufnetwork server for testing
3229
type ServerFixture struct {
33-
t *testing.T
34-
docker *docker
35-
client *tnclient.Client
36-
ctx context.Context
37-
containers struct {
30+
t *testing.T
31+
docker *docker
32+
ctx context.Context
33+
Endpoint string
34+
ManagerPrivateKey *kwilcrypto.Secp256k1PrivateKey
35+
DBOwnerPrivateKey *kwilcrypto.Secp256k1PrivateKey
36+
containers struct {
3837
postgres containerSpec
3938
tndb containerSpec
4039
}
@@ -61,11 +60,22 @@ type docker struct {
6160
func NewServerFixture(t *testing.T) *ServerFixture {
6261
ctx := context.Background()
6362
d := newDocker(t)
63+
managerPk, err := kwilcrypto.Secp256k1PrivateKeyFromHex(managerPrivateKey)
64+
if err != nil {
65+
t.Fatalf("failed to parse manager private key: %v", err)
66+
}
67+
dbOwnerPk, err := kwilcrypto.Secp256k1PrivateKeyFromHex(DB_PRIVATE_KEY)
68+
if err != nil {
69+
t.Fatalf("failed to parse db owner private key: %v", err)
70+
}
6471

6572
return &ServerFixture{
66-
t: t,
67-
docker: d,
68-
ctx: ctx,
73+
t: t,
74+
docker: d,
75+
ctx: ctx,
76+
Endpoint: TestKwilProvider,
77+
ManagerPrivateKey: managerPk,
78+
DBOwnerPrivateKey: dbOwnerPk,
6979
containers: struct {
7080
postgres containerSpec
7181
tndb containerSpec
@@ -188,7 +198,12 @@ func (s *ServerFixture) Setup() error {
188198
} else {
189199
providerArg := fmt.Sprintf("PROVIDER=%s", TestKwilProvider)
190200
privateKeyArg := fmt.Sprintf("PRIVATE_KEY=%s", DB_PRIVATE_KEY)
191-
migrateCmd := exec.CommandContext(s.ctx, "task", "action:migrate", providerArg, privateKeyArg)
201+
// derive 0x-address from manager private key
202+
mgrPk, _ := kwilcrypto.Secp256k1PrivateKeyFromHex(managerPrivateKey)
203+
mgrSigner := &auth.EthPersonalSigner{Key: *mgrPk}
204+
mgrAddr, _ := auth.EthSecp256k1Authenticator{}.Identifier(mgrSigner.CompactID())
205+
adminWalletArg := fmt.Sprintf("ADMIN_WALLET=%s", mgrAddr)
206+
migrateCmd := exec.CommandContext(s.ctx, "task", "action:migrate", providerArg, privateKeyArg, adminWalletArg)
192207
migrateCmd.Dir = nodeRepoDir
193208

194209
s.t.Logf("Executing command in %s: %s", migrateCmd.Dir, strings.Join(migrateCmd.Args, " "))
@@ -199,84 +214,6 @@ func (s *ServerFixture) Setup() error {
199214
}
200215
s.t.Logf("Migration task successful. Output: %s", string(migrateOut))
201216
}
202-
203-
// Create client
204-
s.t.Log("Creating private key...")
205-
pk, err := kwilcrypto.Secp256k1PrivateKeyFromHex(TestPrivateKey)
206-
if err != nil {
207-
return fmt.Errorf("failed to parse private key: %w", err)
208-
}
209-
s.t.Log("Successfully created private key")
210-
211-
s.t.Log("Creating TN client...")
212-
s.t.Logf("Using provider: %s", TestKwilProvider)
213-
214-
// Get the Ethereum address from the public key
215-
pubKeyBytes := pk.Public().Bytes()
216-
// Remove the first byte which is the compression flag
217-
pubKeyBytes = pubKeyBytes[1:]
218-
addr, err := util.NewEthereumAddressFromBytes(crypto.Keccak256(pubKeyBytes)[12:])
219-
if err != nil {
220-
return fmt.Errorf("failed to get address from public key: %w", err)
221-
}
222-
s.t.Logf("Using signer with address: %s", addr.Address())
223-
224-
s.t.Log("Attempting to create client...")
225-
var lastErr error
226-
for i := 0; i < 60; i++ { // 60 seconds max wait
227-
s.t.Logf("Attempt %d/60: Creating client with provider URL %s", i+1, TestKwilProvider)
228-
229-
// First check if the server is accepting connections
230-
cmd := exec.Command("curl", "-s", "-w", "\n%{http_code}", "http://localhost:8484/api/v1/health")
231-
out, err := cmd.CombinedOutput()
232-
if err != nil {
233-
lastErr = fmt.Errorf("health check command failed: %w", err)
234-
s.t.Logf("Health check command failed: %v", err)
235-
time.Sleep(time.Second)
236-
continue
237-
}
238-
239-
// Split output into response body and status code
240-
parts := strings.Split(string(out), "\n")
241-
if len(parts) != 2 {
242-
lastErr = fmt.Errorf("unexpected health check output format: %s", string(out))
243-
s.t.Logf("Health check output format error: %s", string(out))
244-
time.Sleep(time.Second)
245-
continue
246-
}
247-
248-
statusCode := strings.TrimSpace(parts[1])
249-
s.t.Logf("Health check response - Status: %s", statusCode)
250-
if statusCode != "200" {
251-
lastErr = fmt.Errorf("health check returned non-200 status: %s", statusCode)
252-
s.t.Logf("Health check failed with status %s", statusCode)
253-
time.Sleep(time.Second)
254-
continue
255-
}
256-
257-
s.t.Log("Health check passed, attempting to create client...")
258-
// Try to create the client now that we know the server is accepting connections
259-
s.client, err = tnclient.NewClient(
260-
s.ctx,
261-
TestKwilProvider,
262-
tnclient.WithSigner(&auth.EthPersonalSigner{Key: *pk}),
263-
)
264-
if err != nil {
265-
lastErr = fmt.Errorf("failed to create TN client: %w", err)
266-
s.t.Logf("Client creation failed: %v", err)
267-
time.Sleep(time.Second)
268-
continue
269-
}
270-
271-
// Successfully created client
272-
s.t.Log("Client created successfully")
273-
break
274-
}
275-
276-
if s.client == nil {
277-
return fmt.Errorf("failed to create client after 60 attempts. Last error: %w", lastErr)
278-
}
279-
280217
return nil
281218
}
282219

@@ -293,11 +230,6 @@ func (s *ServerFixture) Teardown() {
293230
s.docker.cleanup()
294231
}
295232

296-
// Client returns the client for interacting with the server
297-
func (s *ServerFixture) Client() *tnclient.Client {
298-
return s.client
299-
}
300-
301233
// newDocker creates a new docker helper
302234
func newDocker(t *testing.T) *docker {
303235
return &docker{t: t}

0 commit comments

Comments
 (0)