itest: parallelize test steps
This commit is contained in:
parent
9862ee7cd6
commit
decd2d975c
@ -7,7 +7,6 @@ import (
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
@ -18,246 +17,332 @@ import (
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
// errContains is a helper function that returns true if a string is contained
|
||||
// in the message of an error.
|
||||
func errContains(err error, str string) bool {
|
||||
return strings.Contains(err.Error(), str)
|
||||
}
|
||||
|
||||
// testMacaroonAuthentication makes sure that if macaroon authentication is
|
||||
// enabled on the gRPC interface, no requests with missing or invalid
|
||||
// macaroons are allowed. Further, the specific access rights (read/write,
|
||||
// entity based) and first-party caveats are tested as well.
|
||||
func testMacaroonAuthentication(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
var (
|
||||
ctxb = context.Background()
|
||||
infoReq = &lnrpc.GetInfoRequest{}
|
||||
newAddrReq = &lnrpc.NewAddressRequest{
|
||||
Type: AddrTypeWitnessPubkeyHash,
|
||||
}
|
||||
testNode = net.Alice
|
||||
)
|
||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
// First test: Make sure we get an error if we use no macaroons but try
|
||||
// to connect to a node that has macaroon authentication enabled.
|
||||
conn, err := testNode.ConnectRPC(false)
|
||||
require.NoError(t.t, err)
|
||||
defer conn.Close()
|
||||
client := lnrpc.NewLightningClient(conn)
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
if err == nil || !errContains(err, "expected 1 macaroon") {
|
||||
t.Fatalf("expected to get an error when connecting without " +
|
||||
"macaroons")
|
||||
testCases := []struct {
|
||||
name string
|
||||
run func(ctxt context.Context, t *testing.T)
|
||||
}{{
|
||||
// First test: Make sure we get an error if we use no macaroons
|
||||
// but try to connect to a node that has macaroon authentication
|
||||
// enabled.
|
||||
name: "no macaroon",
|
||||
run: func(ctxt context.Context, t *testing.T) {
|
||||
conn, err := testNode.ConnectRPC(false)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = conn.Close() }()
|
||||
client := lnrpc.NewLightningClient(conn)
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "expected 1 macaroon")
|
||||
},
|
||||
}, {
|
||||
// Second test: Ensure that an invalid macaroon also triggers an
|
||||
// error.
|
||||
name: "invalid macaroon",
|
||||
run: func(ctxt context.Context, t *testing.T) {
|
||||
invalidMac, _ := macaroon.New(
|
||||
[]byte("dummy_root_key"), []byte("0"), "itest",
|
||||
macaroon.LatestVersion,
|
||||
)
|
||||
cleanup, client := macaroonClient(
|
||||
t, testNode, invalidMac,
|
||||
)
|
||||
defer cleanup()
|
||||
_, err := client.GetInfo(ctxt, infoReq)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "cannot get macaroon")
|
||||
},
|
||||
}, {
|
||||
// Third test: Try to access a write method with read-only
|
||||
// macaroon.
|
||||
name: "read only macaroon",
|
||||
run: func(ctxt context.Context, t *testing.T) {
|
||||
readonlyMac, err := testNode.ReadMacaroon(
|
||||
testNode.ReadMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
cleanup, client := macaroonClient(
|
||||
t, testNode, readonlyMac,
|
||||
)
|
||||
defer cleanup()
|
||||
_, err = client.NewAddress(ctxt, newAddrReq)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "permission denied")
|
||||
},
|
||||
}, {
|
||||
// Fourth test: Check first-party caveat with timeout that
|
||||
// expired 30 seconds ago.
|
||||
name: "expired macaroon",
|
||||
run: func(ctxt context.Context, t *testing.T) {
|
||||
readonlyMac, err := testNode.ReadMacaroon(
|
||||
testNode.ReadMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
timeoutMac, err := macaroons.AddConstraints(
|
||||
readonlyMac, macaroons.TimeoutConstraint(-30),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
cleanup, client := macaroonClient(
|
||||
t, testNode, timeoutMac,
|
||||
)
|
||||
defer cleanup()
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "macaroon has expired")
|
||||
},
|
||||
}, {
|
||||
// Fifth test: Check first-party caveat with invalid IP address.
|
||||
name: "invalid IP macaroon",
|
||||
run: func(ctxt context.Context, t *testing.T) {
|
||||
readonlyMac, err := testNode.ReadMacaroon(
|
||||
testNode.ReadMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
invalidIpAddrMac, err := macaroons.AddConstraints(
|
||||
readonlyMac, macaroons.IPLockConstraint(
|
||||
"1.1.1.1",
|
||||
),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
cleanup, client := macaroonClient(
|
||||
t, testNode, invalidIpAddrMac,
|
||||
)
|
||||
defer cleanup()
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "different IP address")
|
||||
},
|
||||
}, {
|
||||
// Sixth test: Make sure that if we do everything correct and
|
||||
// send the admin macaroon with first-party caveats that we can
|
||||
// satisfy, we get a correct answer.
|
||||
name: "correct macaroon",
|
||||
run: func(ctxt context.Context, t *testing.T) {
|
||||
adminMac, err := testNode.ReadMacaroon(
|
||||
testNode.AdminMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
adminMac, err = macaroons.AddConstraints(
|
||||
adminMac, macaroons.TimeoutConstraint(30),
|
||||
macaroons.IPLockConstraint("127.0.0.1"),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
cleanup, client := macaroonClient(t, testNode, adminMac)
|
||||
defer cleanup()
|
||||
res, err := client.NewAddress(ctxt, newAddrReq)
|
||||
require.NoError(t, err, "get new address")
|
||||
assert.Contains(t, res.Address, "bcrt1")
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctxt, cancel := context.WithTimeout(
|
||||
context.Background(), defaultTimeout,
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
tc.run(ctxt, t)
|
||||
})
|
||||
}
|
||||
|
||||
// Second test: Ensure that an invalid macaroon also triggers an error.
|
||||
invalidMac, _ := macaroon.New(
|
||||
[]byte("dummy_root_key"), []byte("0"), "itest",
|
||||
macaroon.LatestVersion,
|
||||
)
|
||||
cleanup, client := macaroonClient(t.t, testNode, invalidMac)
|
||||
defer cleanup()
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
if err == nil || !errContains(err, "cannot get macaroon") {
|
||||
t.Fatalf("expected to get an error when connecting with an " +
|
||||
"invalid macaroon")
|
||||
}
|
||||
|
||||
// Third test: Try to access a write method with read-only macaroon.
|
||||
readonlyMac, err := testNode.ReadMacaroon(
|
||||
testNode.ReadMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
cleanup, client = macaroonClient(t.t, testNode, readonlyMac)
|
||||
defer cleanup()
|
||||
_, err = client.NewAddress(ctxt, newAddrReq)
|
||||
if err == nil || !errContains(err, "permission denied") {
|
||||
t.Fatalf("expected to get an error when connecting to " +
|
||||
"write method with read-only macaroon")
|
||||
}
|
||||
|
||||
// Fourth test: Check first-party caveat with timeout that expired
|
||||
// 30 seconds ago.
|
||||
timeoutMac, err := macaroons.AddConstraints(
|
||||
readonlyMac, macaroons.TimeoutConstraint(-30),
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
cleanup, client = macaroonClient(t.t, testNode, timeoutMac)
|
||||
defer cleanup()
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
if err == nil || !errContains(err, "macaroon has expired") {
|
||||
t.Fatalf("expected to get an error when connecting with an " +
|
||||
"invalid macaroon")
|
||||
}
|
||||
|
||||
// Fifth test: Check first-party caveat with invalid IP address.
|
||||
invalidIpAddrMac, err := macaroons.AddConstraints(
|
||||
readonlyMac, macaroons.IPLockConstraint("1.1.1.1"),
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
cleanup, client = macaroonClient(t.t, testNode, invalidIpAddrMac)
|
||||
defer cleanup()
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
if err == nil || !errContains(err, "different IP address") {
|
||||
t.Fatalf("expected to get an error when connecting with an " +
|
||||
"invalid macaroon")
|
||||
}
|
||||
|
||||
// Sixth test: Make sure that if we do everything correct and send
|
||||
// the admin macaroon with first-party caveats that we can satisfy,
|
||||
// we get a correct answer.
|
||||
adminMac, err := testNode.ReadMacaroon(
|
||||
testNode.AdminMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
adminMac, err = macaroons.AddConstraints(
|
||||
adminMac, macaroons.TimeoutConstraint(30),
|
||||
macaroons.IPLockConstraint("127.0.0.1"),
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
cleanup, client = macaroonClient(t.t, testNode, adminMac)
|
||||
defer cleanup()
|
||||
res, err := client.NewAddress(ctxt, newAddrReq)
|
||||
require.NoError(t.t, err, "get new address")
|
||||
assert.Contains(t.t, res.Address, "bcrt1")
|
||||
}
|
||||
|
||||
// testBakeMacaroon checks that when creating macaroons, the permissions param
|
||||
// in the request must be set correctly, and the baked macaroon has the intended
|
||||
// permissions.
|
||||
func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
var (
|
||||
ctxb = context.Background()
|
||||
req = &lnrpc.BakeMacaroonRequest{}
|
||||
testNode = net.Alice
|
||||
)
|
||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||
defer cancel()
|
||||
var testNode = net.Alice
|
||||
|
||||
// First test: when the permission list is empty in the request, an error
|
||||
// should be returned.
|
||||
adminMac, err := testNode.ReadMacaroon(
|
||||
testNode.AdminMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t.t, err)
|
||||
cleanup, client := macaroonClient(t.t, testNode, adminMac)
|
||||
defer cleanup()
|
||||
_, err = client.BakeMacaroon(ctxt, req)
|
||||
if err == nil || !errContains(err, "permission list cannot be empty") {
|
||||
t.Fatalf("expected an error, got %v", err)
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
run func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient)
|
||||
}{{
|
||||
// First test: when the permission list is empty in the request,
|
||||
// an error should be returned.
|
||||
name: "no permission list",
|
||||
run: func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient) {
|
||||
|
||||
// Second test: when the action in the permission list is not valid,
|
||||
// an error should be returned.
|
||||
req = &lnrpc.BakeMacaroonRequest{
|
||||
Permissions: []*lnrpc.MacaroonPermission{
|
||||
{
|
||||
Entity: "macaroon",
|
||||
Action: "invalid123",
|
||||
},
|
||||
req := &lnrpc.BakeMacaroonRequest{}
|
||||
_, err := adminClient.BakeMacaroon(ctxt, req)
|
||||
require.Error(t, err)
|
||||
assert.Contains(
|
||||
t, err.Error(), "permission list cannot be "+
|
||||
"empty",
|
||||
)
|
||||
},
|
||||
}
|
||||
_, err = client.BakeMacaroon(ctxt, req)
|
||||
if err == nil || !errContains(err, "invalid permission action") {
|
||||
t.Fatalf("expected an error, got %v", err)
|
||||
}
|
||||
}, {
|
||||
// Second test: when the action in the permission list is not
|
||||
// valid, an error should be returned.
|
||||
name: "invalid permission list",
|
||||
run: func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient) {
|
||||
|
||||
// Third test: when the entity in the permission list is not valid,
|
||||
// an error should be returned.
|
||||
req = &lnrpc.BakeMacaroonRequest{
|
||||
Permissions: []*lnrpc.MacaroonPermission{
|
||||
{
|
||||
Entity: "invalid123",
|
||||
Action: "read",
|
||||
},
|
||||
req := &lnrpc.BakeMacaroonRequest{
|
||||
Permissions: []*lnrpc.MacaroonPermission{{
|
||||
Entity: "macaroon",
|
||||
Action: "invalid123",
|
||||
}},
|
||||
}
|
||||
_, err := adminClient.BakeMacaroon(ctxt, req)
|
||||
require.Error(t, err)
|
||||
assert.Contains(
|
||||
t, err.Error(), "invalid permission action",
|
||||
)
|
||||
},
|
||||
}
|
||||
_, err = client.BakeMacaroon(ctxt, req)
|
||||
if err == nil || !errContains(err, "invalid permission entity") {
|
||||
t.Fatalf("expected an error, got %v", err)
|
||||
}
|
||||
}, {
|
||||
// Third test: when the entity in the permission list is not
|
||||
// valid, an error should be returned.
|
||||
name: "invalid permission entity",
|
||||
run: func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient) {
|
||||
|
||||
// Fourth test: check that when no root key ID is specified, the default
|
||||
// root key ID is used.
|
||||
req = &lnrpc.BakeMacaroonRequest{
|
||||
Permissions: []*lnrpc.MacaroonPermission{
|
||||
{
|
||||
Entity: "macaroon",
|
||||
Action: "read",
|
||||
},
|
||||
req := &lnrpc.BakeMacaroonRequest{
|
||||
Permissions: []*lnrpc.MacaroonPermission{{
|
||||
Entity: "invalid123",
|
||||
Action: "read",
|
||||
}},
|
||||
}
|
||||
_, err := adminClient.BakeMacaroon(ctxt, req)
|
||||
require.Error(t, err)
|
||||
assert.Contains(
|
||||
t, err.Error(), "invalid permission entity",
|
||||
)
|
||||
},
|
||||
}
|
||||
_, err = client.BakeMacaroon(ctxt, req)
|
||||
require.NoError(t.t, err)
|
||||
}, {
|
||||
// Fourth test: check that when no root key ID is specified, the
|
||||
// default root keyID is used.
|
||||
name: "default root key ID",
|
||||
run: func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient) {
|
||||
|
||||
listReq := &lnrpc.ListMacaroonIDsRequest{}
|
||||
resp, err := client.ListMacaroonIDs(ctxt, listReq)
|
||||
require.NoError(t.t, err)
|
||||
if resp.RootKeyIds[0] != 0 {
|
||||
t.Fatalf("expected ID to be 0, found: %v", resp.RootKeyIds)
|
||||
}
|
||||
req := &lnrpc.BakeMacaroonRequest{
|
||||
Permissions: []*lnrpc.MacaroonPermission{{
|
||||
Entity: "macaroon",
|
||||
Action: "read",
|
||||
}},
|
||||
}
|
||||
_, err := adminClient.BakeMacaroon(ctxt, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fifth test: create a macaroon use a non-default root key ID.
|
||||
rootKeyID := uint64(4200)
|
||||
req = &lnrpc.BakeMacaroonRequest{
|
||||
RootKeyId: rootKeyID,
|
||||
Permissions: []*lnrpc.MacaroonPermission{
|
||||
{
|
||||
Entity: "macaroon",
|
||||
Action: "read",
|
||||
},
|
||||
listReq := &lnrpc.ListMacaroonIDsRequest{}
|
||||
resp, err := adminClient.ListMacaroonIDs(ctxt, listReq)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp.RootKeyIds[0], uint64(0))
|
||||
},
|
||||
}
|
||||
bakeResp, err := client.BakeMacaroon(ctxt, req)
|
||||
require.NoError(t.t, err)
|
||||
}, {
|
||||
// Fifth test: create a macaroon use a non-default root key ID.
|
||||
name: "custom root key ID",
|
||||
run: func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient) {
|
||||
|
||||
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||
resp, err = client.ListMacaroonIDs(ctxt, listReq)
|
||||
require.NoError(t.t, err)
|
||||
rootKeyID := uint64(4200)
|
||||
req := &lnrpc.BakeMacaroonRequest{
|
||||
RootKeyId: rootKeyID,
|
||||
Permissions: []*lnrpc.MacaroonPermission{{
|
||||
Entity: "macaroon",
|
||||
Action: "read",
|
||||
}},
|
||||
}
|
||||
_, err := adminClient.BakeMacaroon(ctxt, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// the ListMacaroonIDs should give a list of two IDs, the default ID 0, and
|
||||
// the newly created ID. The returned response is sorted to guarantee the
|
||||
// order so that we can compare them one by one.
|
||||
sort.Slice(resp.RootKeyIds, func(i, j int) bool {
|
||||
return resp.RootKeyIds[i] < resp.RootKeyIds[j]
|
||||
})
|
||||
if resp.RootKeyIds[0] != 0 {
|
||||
t.Fatalf("expected ID to be %v, found: %v", 0, resp.RootKeyIds[0])
|
||||
}
|
||||
if resp.RootKeyIds[1] != rootKeyID {
|
||||
t.Fatalf(
|
||||
"expected ID to be %v, found: %v",
|
||||
rootKeyID, resp.RootKeyIds[1],
|
||||
)
|
||||
}
|
||||
listReq := &lnrpc.ListMacaroonIDsRequest{}
|
||||
resp, err := adminClient.ListMacaroonIDs(ctxt, listReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sixth test: check the baked macaroon has the intended permissions. It
|
||||
// should succeed in reading, and fail to write a macaroon.
|
||||
newMac, err := readMacaroonFromHex(bakeResp.Macaroon)
|
||||
require.NoError(t.t, err)
|
||||
cleanup, client = macaroonClient(t.t, testNode, newMac)
|
||||
defer cleanup()
|
||||
// the ListMacaroonIDs should give a list of two IDs,
|
||||
// the default ID 0, and the newly created ID. The
|
||||
// returned response is sorted to guarantee the order so
|
||||
// that we can compare them one by one.
|
||||
sort.Slice(resp.RootKeyIds, func(i, j int) bool {
|
||||
return resp.RootKeyIds[i] < resp.RootKeyIds[j]
|
||||
})
|
||||
require.Equal(t, resp.RootKeyIds[0], uint64(0))
|
||||
require.Equal(t, resp.RootKeyIds[1], rootKeyID)
|
||||
},
|
||||
}, {
|
||||
// Sixth test: check the baked macaroon has the intended
|
||||
// permissions. It should succeed in reading, and fail to write
|
||||
// a macaroon.
|
||||
name: "custom macaroon permissions",
|
||||
run: func(ctxt context.Context, t *testing.T,
|
||||
adminClient lnrpc.LightningClient) {
|
||||
|
||||
// BakeMacaroon requires a write permission, so this call should return an
|
||||
// error.
|
||||
_, err = client.BakeMacaroon(ctxt, req)
|
||||
if err == nil || !errContains(err, "permission denied") {
|
||||
t.Fatalf("expected an error, got %v", err)
|
||||
}
|
||||
rootKeyID := uint64(4200)
|
||||
req := &lnrpc.BakeMacaroonRequest{
|
||||
RootKeyId: rootKeyID,
|
||||
Permissions: []*lnrpc.MacaroonPermission{{
|
||||
Entity: "macaroon",
|
||||
Action: "read",
|
||||
}},
|
||||
}
|
||||
bakeResp, err := adminClient.BakeMacaroon(ctxt, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// ListMacaroon requires a read permission, so this call should succeed.
|
||||
listReq = &lnrpc.ListMacaroonIDsRequest{}
|
||||
resp, err = client.ListMacaroonIDs(ctxt, listReq)
|
||||
require.NoError(t.t, err)
|
||||
newMac, err := readMacaroonFromHex(bakeResp.Macaroon)
|
||||
require.NoError(t, err)
|
||||
cleanup, readOnlyClient := macaroonClient(
|
||||
t, testNode, newMac,
|
||||
)
|
||||
defer cleanup()
|
||||
|
||||
// Current macaroon can only work on entity macaroon, so a GetInfo request
|
||||
// will fail.
|
||||
infoReq := &lnrpc.GetInfoRequest{}
|
||||
_, err = client.GetInfo(ctxt, infoReq)
|
||||
if err == nil || !errContains(err, "permission denied") {
|
||||
t.Fatalf("expected error not returned, got %v", err)
|
||||
// BakeMacaroon requires a write permission, so this
|
||||
// call should return an error.
|
||||
_, err = readOnlyClient.BakeMacaroon(ctxt, req)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "permission denied")
|
||||
|
||||
// ListMacaroon requires a read permission, so this call
|
||||
// should succeed.
|
||||
listReq := &lnrpc.ListMacaroonIDsRequest{}
|
||||
_, err = readOnlyClient.ListMacaroonIDs(ctxt, listReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Current macaroon can only work on entity macaroon, so
|
||||
// a GetInfo request will fail.
|
||||
infoReq := &lnrpc.GetInfoRequest{}
|
||||
_, err = readOnlyClient.GetInfo(ctxt, infoReq)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "permission denied")
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctxt, cancel := context.WithTimeout(
|
||||
context.Background(), defaultTimeout,
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
adminMac, err := testNode.ReadMacaroon(
|
||||
testNode.AdminMacPath(), defaultTimeout,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
cleanup, client := macaroonClient(t, testNode, adminMac)
|
||||
defer cleanup()
|
||||
|
||||
tc.run(ctxt, t, client)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user