config_test.gno

12.88 Kb ยท 308 lines
  1package config
  2
  3import (
  4	"errors"
  5	"std"
  6	"testing"
  7
  8	"gno.land/p/demo/testutils"
  9	"gno.land/p/demo/uassert"
 10	"gno.land/p/moul/authz"
 11)
 12
 13var (
 14	originAddr     = testutils.TestAddress("origin")
 15	manager1Addr   = testutils.TestAddress("manager1")
 16	manager2Addr   = testutils.TestAddress("manager2")
 17	nonManagerAddr = testutils.TestAddress("nonManager")
 18)
 19
 20// Helper to reset the Authorizer for each test, simulating initialization.
 21func setupTest(cur realm, t *testing.T) {
 22	t.Helper()
 23
 24	// Set the initial caller context
 25	testing.SetRealm(std.NewUserRealm(originAddr))
 26	// Initialize the Authorizer with the originAddr as the sole member,
 27	// simulating the state after NewWithOrigin() in a real deployment.
 28	Authorizer = authz.NewWithAuthority(authz.NewMemberAuthority(originAddr))
 29	// Ensure the origin address is the initial manager
 30	uassert.True(t, HasManager(cur, originAddr), "origin should be the initial manager")
 31}
 32
 33func TestAddManager(cur realm, t *testing.T) {
 34	setupTest(cur, t)
 35
 36	// Origin adds manager1 - Should succeed
 37	testing.SetRealm(std.NewUserRealm(originAddr))
 38	err := AddManager(cross, manager1Addr)
 39	uassert.NoError(t, err, "origin adding manager1 should succeed")
 40	uassert.True(t, HasManager(cur, manager1Addr), "manager1 should now be a manager")
 41
 42	// Non-manager tries to add manager2 - Should fail
 43	testing.SetRealm(std.NewUserRealm(nonManagerAddr))
 44	err = AddManager(cross, manager2Addr)
 45	uassert.Error(t, err, "non-manager adding manager2 should fail")
 46	uassert.False(t, HasManager(cur, manager2Addr), "manager2 should not have been added")
 47
 48	// Manager1 adds manager2 - Should succeed
 49	testing.SetRealm(std.NewUserRealm(manager1Addr))
 50	err = AddManager(cross, manager2Addr)
 51	uassert.NoError(t, err, "manager1 adding manager2 should succeed")
 52	uassert.True(t, HasManager(cur, manager2Addr), "manager2 should now be a manager")
 53
 54	// Transfer authority away from MemberAuthority
 55	testing.SetRealm(std.NewUserRealm(originAddr)) // Origin transfers
 56	err = TransferManagement(cross, authz.NewAutoAcceptAuthority())
 57	uassert.NoError(t, err, "transferring authority should succeed")
 58
 59	// Try adding after transfer - Should fail (wrong authority type)
 60	testing.SetRealm(std.NewUserRealm(manager1Addr))
 61	err = AddManager(cross, nonManagerAddr) // Try adding someone new
 62	uassert.ErrorContains(t, err, "current authority is not a MemberAuthority", "adding manager should fail after transfer")
 63}
 64
 65func TestRemoveManager(cur realm, t *testing.T) {
 66	setupTest(cur, t)
 67
 68	// Add manager1 first
 69	testing.SetRealm(std.NewUserRealm(originAddr))
 70	err := AddManager(cross, manager1Addr)
 71	uassert.NoError(t, err, "setup: failed to add manager1")
 72	uassert.True(t, HasManager(cur, manager1Addr), "setup: manager1 should be added")
 73
 74	// Non-manager tries to remove manager1 - Should fail
 75	testing.SetRealm(std.NewUserRealm(nonManagerAddr))
 76	err = RemoveManager(cross, manager1Addr)
 77	uassert.Error(t, err, "non-manager removing manager1 should fail")
 78	uassert.True(t, HasManager(cur, manager1Addr), "manager1 should still be a manager")
 79
 80	// Origin removes manager1 - Should succeed
 81	testing.SetRealm(std.NewUserRealm(originAddr))
 82	err = RemoveManager(cross, manager1Addr)
 83	uassert.NoError(t, err, "origin removing manager1 should succeed")
 84	uassert.False(t, HasManager(cur, manager1Addr), "manager1 should now be removed")
 85
 86	// Add manager1 again for next test case
 87	testing.SetRealm(std.NewUserRealm(originAddr))
 88	err = AddManager(cross, manager1Addr)
 89	uassert.NoError(t, err, "setup: failed to re-add manager1")
 90
 91	// Transfer authority
 92	testing.SetRealm(std.NewUserRealm(originAddr))
 93	err = TransferManagement(cross, authz.NewAutoAcceptAuthority())
 94	uassert.NoError(t, err, "transferring authority should succeed")
 95
 96	// Try removing after transfer - Should fail (wrong authority type)
 97	testing.SetRealm(std.NewUserRealm(originAddr)) // Use origin, doesn't matter which user now
 98	err = RemoveManager(cross, manager1Addr)
 99	uassert.ErrorContains(t, err, "current authority is not a MemberAuthority", "removing manager should fail after transfer")
100}
101
102func TestListManagers(cur realm, t *testing.T) {
103	setupTest(cur, t)
104	initialList := ListManagers(cross)
105	assertAddrSliceEqual(t, []std.Address{originAddr}, initialList)
106	// Add manager1 and manager2
107	testing.SetRealm(std.NewUserRealm(originAddr))
108	err := AddManager(cross, manager1Addr)
109	uassert.NoError(t, err)
110	err = AddManager(cross, manager2Addr)
111	uassert.NoError(t, err)
112
113	// List should contain origin, manager1, manager2
114	list1 := ListManagers(cross)
115	expected1 := []std.Address{manager2Addr, manager1Addr, originAddr}
116	assertAddrSliceEqual(t, expected1, list1)
117
118	// Remove manager1
119	testing.SetRealm(std.NewUserRealm(originAddr)) // Can be origin or manager2
120	err = RemoveManager(cross, manager1Addr)
121	uassert.NoError(t, err)
122
123	// List should contain origin, manager2
124	list2 := ListManagers(cross)
125	expected2 := []std.Address{manager2Addr, originAddr}
126	assertAddrSliceEqual(t, expected2, list2)
127
128	// Transfer authority
129	testing.SetRealm(std.NewUserRealm(originAddr))
130	err = TransferManagement(cross, authz.NewAutoAcceptAuthority())
131	uassert.NoError(t, err)
132
133	// List should be empty after transfer
134	list3 := ListManagers(cross)
135	uassert.True(t, len(list3) == 0, "manager list should be empty after transfer")
136}
137
138func TestHasManager(cur realm, t *testing.T) {
139	setupTest(cur, t)
140
141	// Initially, only origin is manager
142	uassert.True(t, HasManager(cross, originAddr), "origin should initially be a manager")
143	uassert.False(t, HasManager(cross, manager1Addr), "manager1 should not initially be a manager")
144	uassert.False(t, HasManager(cross, nonManagerAddr), "nonManager should not initially be a manager")
145
146	// Add manager1
147	testing.SetRealm(std.NewUserRealm(originAddr))
148	err := AddManager(cross, manager1Addr)
149	uassert.NoError(t, err)
150
151	// Check again
152	uassert.True(t, HasManager(cross, originAddr), "origin should still be a manager")
153	uassert.True(t, HasManager(cross, manager1Addr), "manager1 should now be a manager")
154	uassert.False(t, HasManager(cross, nonManagerAddr), "nonManager should still not be a manager")
155
156	// Transfer authority
157	testing.SetRealm(std.NewUserRealm(originAddr))
158	err = TransferManagement(cross, authz.NewAutoAcceptAuthority())
159	uassert.NoError(t, err)
160
161	// After transfer, HasManager should always return false for MemberAuthority checks
162	uassert.False(t, HasManager(cross, originAddr), "HasManager should be false after transfer")
163	uassert.False(t, HasManager(cross, manager1Addr), "HasManager should be false after transfer")
164	uassert.False(t, HasManager(cross, nonManagerAddr), "HasManager should be false after transfer")
165}
166
167func TestTransferManagement(cur realm, t *testing.T) {
168	setupTest(cur, t)
169
170	// Add manager1
171	testing.SetRealm(std.NewUserRealm(originAddr))
172	err := AddManager(cross, manager1Addr)
173	uassert.NoError(t, err)
174
175	// Create a new authority (MemberAuthority with manager2)
176	newAuthority := authz.NewMemberAuthority(manager2Addr)
177
178	// Non-manager tries to transfer - Should fail
179	testing.SetRealm(std.NewUserRealm(nonManagerAddr))
180	err = TransferManagement(cross, newAuthority)
181	uassert.Error(t, err, "non-manager transfer should fail")
182	_, isMemberAuth := Authorizer.Authority().(*authz.MemberAuthority)
183	uassert.True(t, isMemberAuth, "authority should still be MemberAuthority") // Verify it didn't change
184
185	// Manager1 tries to transfer - Should succeed
186	testing.SetRealm(std.NewUserRealm(manager1Addr))
187	err = TransferManagement(cross, newAuthority)
188	uassert.NoError(t, err, "manager1 transfer should succeed")
189
190	// Verify current authority is the new one
191	currentAuth := Authorizer.Authority()
192	uassert.True(t, currentAuth == newAuthority, "current authority should be the new one")
193
194	// Verify origin is no longer a manager under the *new* authority
195	testing.SetRealm(std.NewUserRealm(manager2Addr)) // Need new manager to check
196	uassert.False(t, HasManager(cross, originAddr), "origin should not be manager under new authority")
197	uassert.False(t, HasManager(cross, manager1Addr), "manager1 should not be manager under new authority")
198	uassert.True(t, HasManager(cross, manager2Addr), "manager2 should be manager under new authority")
199
200	// Try adding a manager using the old origin - Should fail
201	testing.SetRealm(std.NewUserRealm(originAddr))
202	err = AddManager(cross, nonManagerAddr)
203	uassert.Error(t, err, "origin should not be able to add manager after transfer")
204
205	// Try adding a manager using the new manager (manager2) - Should succeed
206	testing.SetRealm(std.NewUserRealm(manager2Addr))
207	err = AddManager(cross, nonManagerAddr)
208	uassert.NoError(t, err, "new manager (manager2) should be able to add managers")
209	uassert.True(t, HasManager(cross, nonManagerAddr), "nonManager should be added by manager2")
210
211	// Try transferring to nil - Should fail
212	testing.SetRealm(std.NewUserRealm(manager2Addr))
213	err = TransferManagement(cross, nil)
214	uassert.ErrorContains(t, err, "new authority cannot be nil", "transferring to nil should fail")
215}
216
217func TestTransferToContractAuthority(cur realm, t *testing.T) {
218	setupTest(cur, t) // Origin is the initial manager
219
220	contractPath := "gno.land/r/testcontract"
221	contractRealm := std.NewCodeRealm(contractPath) // Simulate contract realm
222
223	// Define a simple contract authority handler
224	handlerExecuted := false // Track if the handler itself gets called
225	contractAuth := authz.NewContractAuthority(contractPath, func(title string, action authz.PrivilegedAction) error {
226		// Simulate contract checking the caller *before* executing
227		caller := std.CurrentRealm().Address()
228		expectedContractAddr := std.DerivePkgAddr(contractPath)
229		if caller != expectedContractAddr {
230			// Fail before marking executed or running action
231			// Note: In a real scenario, this handler might just ignore the call
232			// if the caller isn't right, rather than returning an error,
233			// depending on the desired contract logic. Returning an error
234			// here helps the test verify the handler wasn't improperly called.
235			return errors.New("handler: caller is not the contract")
236		}
237
238		// Only mark executed and run action if caller is correct
239		handlerExecuted = true
240		return action()
241	})
242
243	// Origin transfers management to the contract authority
244	testing.SetRealm(std.NewUserRealm(originAddr))
245	err := TransferManagement(cross, contractAuth)
246	uassert.NoError(t, err, "transfer to contract authority failed")
247	uassert.True(t, Authorizer.Authority() == contractAuth, "authority should now be the contract authority")
248
249	// Now, actions like AddManager/RemoveManager should fail because the current
250	// authority is no longer a MemberAuthority. The contract would need its own
251	// logic executed via Authorizer.DoByCurrent() to manage members if desired.
252
253	// Try adding a manager (will check authority type) - Should fail
254	testing.SetRealm(std.NewUserRealm(originAddr)) // Caller doesn't matter for this check
255	err = AddManager(cross, manager1Addr)
256	uassert.ErrorContains(t, err, "current authority is not a MemberAuthority", "AddManager should fail with ContractAuthority")
257
258	// Simulate an action authorized *by the contract* using Authorizer.Do
259	var contractActionExecuted bool
260	handlerExecuted = false         // Reset tracker
261	testing.SetRealm(contractRealm) // Call must originate from the contract now
262	err = Authorizer.DoByCurrent("some_contract_action", func() error {
263		contractActionExecuted = true
264		// Imagine contract logic here
265		return nil
266	})
267	uassert.NoError(t, err, "contract action via Authorizer.Do failed")
268	uassert.True(t, handlerExecuted, "handler should have been executed by contract call") // Verify handler ran
269	uassert.True(t, contractActionExecuted, "contract action should have been executed")
270
271	// Simulate an action from a user - Should fail before handler is called
272	var userActionExecuted bool
273	handlerExecuted = false // Reset tracker
274	testing.SetRealm(std.NewUserRealm(nonManagerAddr))
275	err = Authorizer.DoByCurrent("some_user_action", func() error {
276		userActionExecuted = true
277		return nil
278	})
279	// The ContractAuthority.Authorize method should return an error
280	// because the handler now returns an error if the caller isn't the contract.
281	uassert.Error(t, err, "user action via Authorizer.Do should fail when contract is authority")
282	uassert.ErrorContains(t, err, "handler: caller is not the contract", "error should originate from handler check") // Check specific error
283	uassert.False(t, handlerExecuted, "handler should NOT have been executed by user call")                           // Verify handler didn't run past the check
284	uassert.False(t, userActionExecuted, "user action should not have been executed")
285}
286
287// Helper to check if a slice contains a specific address
288func containsAddr(list []std.Address, addr std.Address) bool {
289	for _, item := range list {
290		if item == addr {
291			return true
292		}
293	}
294	return false
295}
296
297func assertAddrSliceEqual(t *testing.T, expected, actual []std.Address) {
298	t.Helper()
299	if len(expected) != len(actual) {
300		t.Fatalf("expected slice length %d, got %d. Expected: %v, Got: %v", len(expected), len(actual), expected, actual)
301	}
302
303	for i := range expected {
304		if expected[i] != actual[i] {
305			t.Fatalf("slices differ at index %d. Expected: %v, Got: %v", i, expected, actual)
306		}
307	}
308}