...

Source file src/github.com/datawire/ambassador/v2/pkg/agent/api_docs_test.go

Documentation: github.com/datawire/ambassador/v2/pkg/agent

     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  			// Processing the test case snapshot should yield the expected state of the world
   209  			store.ProcessSnapshot(ctx, snapshot)
   210  			assert.Equal(t, c.expectedSOTW, store.StateOfWorld())
   211  
   212  			// Processing an empty snapshot should be ignored and not change the state of the world
   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