1 package agent_test
2
3 import (
4 "context"
5 "net/url"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9
10 "github.com/datawire/ambassador/v2/pkg/agent"
11 amb "github.com/datawire/ambassador/v2/pkg/api/getambassador.io/v3alpha1"
12 "github.com/datawire/ambassador/v2/pkg/kates"
13 snapshotTypes "github.com/datawire/ambassador/v2/pkg/snapshot/v1"
14 "github.com/datawire/dlib/dlog"
15 )
16
17 func TestAPIDocsStore(t *testing.T) {
18 shouldIgnore := true
19 mappingRewrite := "/internal-path"
20 headerValue := "header-value"
21
22 type testCases struct {
23 name string
24 mappings []*amb.Mapping
25
26 rawJSONDocsContent string
27 JSONDocsErr error
28
29 expectedRequestURL string
30 expectedRequestHost string
31 expectedRequestHeaders []agent.Header
32 expectedSOTW []*snapshotTypes.APIDoc
33 }
34 cases := []*testCases{
35 {
36 name: "will ignore mappings without a 'docs' property",
37 mappings: []*amb.Mapping{
38 {
39 Spec: amb.MappingSpec{
40 Prefix: "",
41 Service: "some-svc",
42 Docs: nil,
43 },
44 ObjectMeta: kates.ObjectMeta{
45 Name: "some-endpoint",
46 Namespace: "default",
47 },
48 },
49 nil,
50 },
51 expectedSOTW: []*snapshotTypes.APIDoc{},
52 },
53 {
54 name: "will ignore mappings with docs.ignored setting",
55 mappings: []*amb.Mapping{
56 {
57 Spec: amb.MappingSpec{
58 Prefix: "",
59 Service: "some-svc",
60 Docs: &amb.DocsInfo{
61 Ignored: &shouldIgnore,
62 },
63 },
64 ObjectMeta: kates.ObjectMeta{
65 Name: "some-endpoint",
66 Namespace: "default",
67 },
68 },
69 },
70 expectedSOTW: []*snapshotTypes.APIDoc{},
71 },
72 {
73 name: "will scrape OpenAPI docs from docs path and ignore malformed OpenAPI docs",
74 mappings: []*amb.Mapping{
75 {
76 Spec: amb.MappingSpec{
77 Prefix: "",
78 Rewrite: &mappingRewrite,
79 Service: "https://some-svc.fqdn:443",
80 Docs: &amb.DocsInfo{
81 DisplayName: "docs-display-name",
82 Path: "/docs-location",
83 },
84 Headers: map[string]string{
85 "header-key": headerValue,
86 },
87 },
88 ObjectMeta: kates.ObjectMeta{
89 Name: "some-endpoint",
90 Namespace: "default",
91 },
92 },
93 },
94
95 rawJSONDocsContent: "this is not JSON",
96
97 expectedRequestURL: "https://some-svc.fqdn:443/internal-path/docs-location",
98 expectedRequestHost: "",
99 expectedRequestHeaders: []agent.Header{{Name: "header-key", Value: "header-value"}},
100 expectedSOTW: []*snapshotTypes.APIDoc{},
101 },
102 {
103 name: "will scrape OpenAPI docs from docs path",
104 mappings: []*amb.Mapping{
105 {
106 TypeMeta: kates.TypeMeta{
107 Kind: "Mapping",
108 },
109 Spec: amb.MappingSpec{
110 Prefix: "/prefix",
111 Service: "some-svc:8080",
112 Docs: &amb.DocsInfo{
113 DisplayName: "docs-display-name",
114 Path: "/docs-location",
115 },
116 DeprecatedHost: "mapping-host",
117 Hostname: "mapping-hostname",
118 },
119 ObjectMeta: kates.ObjectMeta{
120 Name: "some-endpoint",
121 Namespace: "default",
122 },
123 },
124 },
125
126 rawJSONDocsContent: `{"openapi":"3.0.0", "info":{"title": "Sample API", "version":"0.0"}, "paths":{}}`,
127
128 expectedRequestURL: "http://some-svc.default:8080/docs-location",
129 expectedRequestHost: "mapping-hostname",
130 expectedRequestHeaders: []agent.Header{},
131 expectedSOTW: []*snapshotTypes.APIDoc{{
132 TypeMeta: &kates.TypeMeta{
133 Kind: "OpenAPI",
134 APIVersion: "v3",
135 },
136 Metadata: &kates.ObjectMeta{
137 Name: "docs-display-name",
138 },
139 TargetRef: &kates.ObjectReference{
140 Kind: "Mapping",
141 Name: "some-endpoint",
142 Namespace: "default",
143 },
144 Data: []byte(`{"components":{},"info":{"title":"Sample API","version":"0.0"},"openapi":"3.0.0","paths":{},"servers":[{"url":"mapping-hostname/prefix"}]}`),
145 }},
146 },
147 {
148 name: "will scrape OpenAPI docs from docs url",
149 mappings: []*amb.Mapping{
150 {
151 TypeMeta: kates.TypeMeta{
152 Kind: "Mapping",
153 },
154 Spec: amb.MappingSpec{
155 Prefix: "/api-prefix",
156 Service: "some-svc",
157 Docs: &amb.DocsInfo{
158 URL: "https://external-url",
159 },
160 Hostname: "*",
161 },
162 ObjectMeta: kates.ObjectMeta{
163 Name: "some-endpoint",
164 Namespace: "default",
165 },
166 },
167 },
168
169 rawJSONDocsContent: `{"openapi":"3.0.0", "info":{"title": "Sample API", "version":"0.0"}, "paths":{}}`,
170
171 expectedRequestURL: "https://external-url",
172 expectedRequestHost: "",
173 expectedRequestHeaders: []agent.Header{},
174 expectedSOTW: []*snapshotTypes.APIDoc{{
175 TypeMeta: &kates.TypeMeta{
176 Kind: "OpenAPI",
177 APIVersion: "v3",
178 },
179 Metadata: &kates.ObjectMeta{
180 Name: "some-endpoint.default",
181 },
182 TargetRef: &kates.ObjectReference{
183 Kind: "Mapping",
184 Name: "some-endpoint",
185 Namespace: "default",
186 },
187 Data: []byte(`{"components":{},"info":{"title":"Sample API","version":"0.0"},"openapi":"3.0.0","paths":{},"servers":[{"url":""}]}`),
188 }},
189 },
190 }
191 for _, c := range cases {
192 c := c
193 t.Run(c.name, func(t *testing.T) {
194 t.Parallel()
195
196 ctx, cancel := context.WithCancel(dlog.NewTestContext(t, false))
197 defer cancel()
198
199 snapshot := &snapshotTypes.Snapshot{
200 Kubernetes: &snapshotTypes.KubernetesSnapshot{
201 Mappings: c.mappings,
202 },
203 }
204
205 store := agent.NewAPIDocsStore()
206 store.Client = NewMockAPIDocsHTTPClient(t, c.expectedRequestURL, c.expectedRequestHost, c.expectedRequestHeaders, c.rawJSONDocsContent, c.JSONDocsErr)
207
208
209 store.ProcessSnapshot(ctx, snapshot)
210 assert.Equal(t, c.expectedSOTW, store.StateOfWorld())
211
212
213 store.ProcessSnapshot(ctx, &snapshotTypes.Snapshot{})
214 assert.Equal(t, c.expectedSOTW, store.StateOfWorld())
215 })
216 }
217 }
218
219 func NewMockAPIDocsHTTPClient(t *testing.T, expectedRequestURL string, expectedRequestHost string, expectedRequestHeaders []agent.Header, content string, err error) agent.APIDocsHTTPClient {
220 return &mockAPIDocsHTTPClient{
221 t: t,
222 expectedRequestURL: expectedRequestURL,
223 expectedRequestHost: expectedRequestHost,
224 expectedRequestHeaders: expectedRequestHeaders,
225 resultContent: content,
226 resultErr: err,
227 }
228 }
229
230 type mockAPIDocsHTTPClient struct {
231 t *testing.T
232
233 expectedRequestURL string
234 expectedRequestHost string
235 expectedRequestHeaders []agent.Header
236
237 resultContent string
238 resultErr error
239 }
240
241 func (c *mockAPIDocsHTTPClient) Get(ctx context.Context, requestURL *url.URL, requestHost string, requestHeaders []agent.Header) ([]byte, error) {
242 if c.expectedRequestURL == "" {
243 c.t.Errorf("unexpected call to APIDocsHTTPClient.Get")
244 c.t.Fail()
245 }
246 assert.Equal(c.t, c.expectedRequestURL, requestURL.String())
247 assert.Equal(c.t, c.expectedRequestHost, requestHost)
248 assert.Equal(c.t, c.expectedRequestHeaders, requestHeaders)
249
250 return []byte(c.resultContent), c.resultErr
251 }
252
View as plain text