Skip to content
File

Blob: tun/client/delegation_test.go

go69 lines
1package client
2 
3import (
4 "context"
5 "errors"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 "time"
11 
12 "go.miragespace.co/specter/spec/protocol"
13 "go.miragespace.co/specter/spec/rpc"
14 
15 "github.com/stretchr/testify/mock"
16 "github.com/stretchr/testify/require"
17 "github.com/twitchtv/twirp"
18)
19 
20func TestDelegationCalls(t *testing.T) {
21 c, m := newOutcomeTestClient(t, nil)
22 c.connections.Store("z-second.example.com", &protocol.Node{Address: "z-second.example.com"})
23 ambiguous := twirp.Unavailable.Error("write result unknown")
24 m.TunnelService.On("MintDelegation", mock.Anything, mock.Anything).Return(nil, ambiguous).Once()
25 _, err := c.MintDelegation(t.Context(), "test", time.Time{})
26 require.ErrorIs(t, err, ErrMintAmbiguous)
27 expected := err.Error()
28 m.TunnelService.AssertNumberOfCalls(t, "MintDelegation", 1)
29 matches := func(address string) any {
30 return mock.MatchedBy(func(ctx context.Context) bool { return rpc.GetNode(ctx).GetAddress() == address })
31 }
32 response := &protocol.MintDelegationResponse{
33 Grant: &protocol.DelegationGrant{
34 Id: strings.Repeat("a", 64),
35 Hostname: "test",
36 },
37 Token: "tg1_secret",
38 }
39 m.TunnelService.On("MintDelegation", matches("gateway.example.com"), mock.Anything).Return(nil, twirp.BadRoute.Error("unsupported")).Once()
40 m.TunnelService.On("MintDelegation", matches("z-second.example.com"), mock.Anything).Return(response, nil).Once()
41 r := httptest.NewRequest(http.MethodPost, "/api/tokens", strings.NewReader(`{"hostname":"test"}`))
42 w := httptest.NewRecorder()
43 c.localHandler().ServeHTTP(w, r)
44 require.Equal(t, http.StatusCreated, w.Code)
45 require.Equal(t, "no-store", w.Header().Get("Cache-Control"))
46 require.Contains(t, w.Body.String(), response.Token)
47 m.TunnelService.On("MintDelegation", mock.Anything, mock.Anything).Return(nil, ambiguous).Once()
48 w = httptest.NewRecorder()
49 c.localHandler().ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/api/tokens", strings.NewReader(`{"hostname":"test"}`)))
50 require.Equal(t, http.StatusInternalServerError, w.Code)
51 require.Contains(t, w.Body.String(), expected)
52 m.TunnelService.On("ListDelegations", matches("gateway.example.com"), mock.Anything).Return(nil, errors.New("temporary failure")).Once()
53 m.TunnelService.On("ListDelegations", matches("z-second.example.com"), mock.Anything).Return(&protocol.ListDelegationsResponse{Grants: []*protocol.DelegationGrant{response.Grant}}, nil).Once()
54 w = httptest.NewRecorder()
55 c.localHandler().ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/tokens", nil))
56 require.Equal(t, http.StatusOK, w.Code)
57 require.NotContains(t, w.Body.String(), "tg1_")
58 require.Contains(t, w.Body.String(), response.Grant.Id)
59 m.TunnelService.On("RevokeDelegation", mock.Anything, mock.Anything).Return(nil, twirp.NotFound.Error("grant is not owned by this client")).Once()
60 w = httptest.NewRecorder()
61 c.localHandler().ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/api/tokens/"+response.Grant.Id+"/revoke", nil))
62 require.Equal(t, http.StatusNotFound, w.Code)
63 m.TunnelService.On("MintDelegation", mock.Anything, mock.Anything).Return(nil, twirp.ResourceExhausted.Error("delegation limit reached")).Once()
64 w = httptest.NewRecorder()
65 c.localHandler().ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/api/tokens", strings.NewReader(`{"hostname":"test"}`)))
66 require.Equal(t, http.StatusTooManyRequests, w.Code)
67 require.NotContains(t, w.Body.String(), "ambiguous")
68}