openapi_test.go

Copyright 2020, 2021, 2022 Red Hat, Inc

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

package
httputils_test

Documentation in literate-programming-style is available at: https://redhatinsights.github.io/insights-operator-utils/packages/http/openapi_test.html


import
(
"errors"
"io/ioutil"
"net/http"
"os"
"testing"
"github.com/stretchr/testify/assert"
httputils
"github.com/RedHatInsights/insights-operator-utils/http"
"github.com/RedHatInsights/insights-operator-utils/tests/helpers"
)
func
TestFilterOutDebugMethods
(
t
*
testing
.
T
)
{
t
.
Run
(
"OK"
,
func
(
t
*
testing
.
T
)
{
for
input
,
expectedOutput
:=
range
map
[
string
]
string
{
openAPIFile
:
openAPIFileWithoutDebugEndpoints
,
}
{
output
,
err
:=
httputils
.
FilterOutDebugMethods
(
input
)
helpers
.
FailOnError
(
t
,
err
)
assert
.
JSONEq
(
t
,
expectedOutput
,
output
)
}
}
)
t
.
Run
(
"Error"
,
func
(
t
*
testing
.
T
)
{
expectedErr
:=
`error unmarshaling JSON: Failed to unmarshal extension properties: `
+
`json: cannot unmarshal string into Go value of type map[string]json.RawMessage`
+
"\n"
+
`Input: "definitely-not-json"`
_
,
err
:=
httputils
.
FilterOutDebugMethods
(
"definitely-not-json"
)
assert
.
EqualError
(
t
,
err
,
expectedErr
)
}
)
}
const
(
openAPIFile
=
` { "openapi": "3.0.0", "info": { "title": "title", "description": "description", "version": "1.0.0" }, "components": {}, "paths": { "/openapi.json": { "get": { "summary": "Returns the OpenAPI specification JSON.", "operationId": "getOpenApi", "responses": { "200": { "description": "A JSON containing the OpenAPI specification for this service." } } } }, "/metrics": { "get": { "summary": "Read all metrics exposed by this service", "description": "Currently the following metrics are exposed to be consumed by Prometheus or any other tool compatible with it: 'consumed_messages' the total number of messages consumed from Kafka, 'consuming_errors' the total number of errors during consuming messages from Kafka, 'successful_messages_processing_time' the time to process successfully message, 'failed_messages_processing_time' the time to process message fail, 'last_checked_timestamp_lag_minutes' shows how slow we get messages from clusters, 'produced_messages' the total number of produced messages, 'written_reports' the total number of reports written to the storage, 'feedback_on_rules' the total number of left feedback, 'sql_queries_counter' the total number of SQL queries, 'sql_queries_durations' the SQL queries durations. Additionally it is possible to consume all metrics provided by Go runtime. There metrics start with 'go_' and 'process_ 'prefixes.", "operationId": "getMetrics", "responses": { "200": { "description": "Default response containing all metrics in semi-structured text format" } } } }, "/organizations": { "get": { "summary": "Returns a list of available organization IDs.", "operationId": "getOrganizations", "description": "[DEBUG ONLY] List of organizations for which at least one Insights report is available via the API.", "responses": { "200": { "description": "A JSON array of organization IDs." } }, "tags": [ "debug" ] } }, "/test": { "get": { "summary": "test", "operationId": "test", "description": "test", "responses": { "200": { "description": "test" } }, "tags": [ "debug" ] }, "post": { "summary": "test", "operationId": "test2", "description": "test", "responses": { "200": { "description": "test" } }, } }, "/organizations/{orgId}/clusters": { "get": { "summary": "Returns a list of clusters associated with the specified organization ID.", "operationId": "getClustersForOrganization", "parameters": [ { "name": "orgId", "in": "path", "required": true, "description": "ID of the requested organization.", "schema": { "type": "integer", "format": "int64", "minimum": 0 } } ], "responses": { "200": { "description": "A JSON array of clusters that belong to the specified organization." } }, "tags": [ "prod" ] } } } }`
openAPIFileWithoutDebugEndpoints
=
` { "openapi": "3.0.0", "info": { "title": "title", "description": "description", "version": "1.0.0" }, "components": {}, "paths": { "/openapi.json": { "get": { "summary": "Returns the OpenAPI specification JSON.", "operationId": "getOpenApi", "responses": { "200": { "description": "A JSON containing the OpenAPI specification for this service." } } } }, "/metrics": { "get": { "summary": "Read all metrics exposed by this service", "description": "Currently the following metrics are exposed to be consumed by Prometheus or any other tool compatible with it: 'consumed_messages' the total number of messages consumed from Kafka, 'consuming_errors' the total number of errors during consuming messages from Kafka, 'successful_messages_processing_time' the time to process successfully message, 'failed_messages_processing_time' the time to process message fail, 'last_checked_timestamp_lag_minutes' shows how slow we get messages from clusters, 'produced_messages' the total number of produced messages, 'written_reports' the total number of reports written to the storage, 'feedback_on_rules' the total number of left feedback, 'sql_queries_counter' the total number of SQL queries, 'sql_queries_durations' the SQL queries durations. Additionally it is possible to consume all metrics provided by Go runtime. There metrics start with 'go_' and 'process_ 'prefixes.", "operationId": "getMetrics", "responses": { "200": { "description": "Default response containing all metrics in semi-structured text format" } } } }, "/test": { "post": { "summary": "test", "operationId": "test2", "description": "test", "responses": { "200": { "description": "test" } } } }, "/organizations/{orgId}/clusters": { "get": { "summary": "Returns a list of clusters associated with the specified organization ID.", "operationId": "getClustersForOrganization", "parameters": [ { "name": "orgId", "in": "path", "required": true, "description": "ID of the requested organization.", "schema": { "type": "integer", "format": "int64", "minimum": 0 } } ], "responses": { "200": { "description": "A JSON array of clusters that belong to the specified organization." } }, "tags": [ "prod" ] } } } }`
)

ResponseWriterMock is mock for http.ResponseWriter

type
ResponseWriterMock
struct
{
headerCalls
int
writeCalls
int
writeHeaderCalls
int
writeShouldFail
bool
}

NewResponseWriterMock is constructor of ResponseWriterMock struct. Constructor takes care of all mock sub-structs, call counters etc.

func
NewResponseWriterMock
(
writeShouldFail
bool
)
ResponseWriterMock
{
return
ResponseWriterMock
{
headerCalls
:
0
,
writeCalls
:
0
,
writeHeaderCalls
:
0
,
writeShouldFail
:
writeShouldFail
,
}
}

Header is a method that needs to be implemented in order to satisfy ResponseWriter interface

func
(
w
*
ResponseWriterMock
)
Header
(
)
http
.
Header
{
w
.
headerCalls
++
return
http
.
Header
{
}
}

Write is a method that needs to be implemented in order to satisfy ResponseWriter interface

func
(
w
*
ResponseWriterMock
)
Write
(
[
]
byte
)
(
int
,
error
)
{
w
.
writeCalls
++
if
w
.
writeShouldFail
{
return
-
1
,
errors
.
New
(
"Mocked error"
)
}
return
1
,
nil
}

WriteHeader is a method that needs to be implemented in order to satisfy ResponseWriter interface

func
(
w
*
ResponseWriterMock
)
WriteHeader
(
statusCode
int
)
{
w
.
writeHeaderCalls
++
}

TestCreateAPIHandlerEmptyFilepath test the function CreateOpenAPIHandler when empty file name is provided

func
TestCreateAPIHandlerEmptyFilepath
(
t
*
testing
.
T
)
{
handler
:=
httputils
.
CreateOpenAPIHandler
(
""
,
true
,
false
)

use mock instead of real http.ResponseWriter struct

	
writer
:=
NewResponseWriterMock
(
false
)

try to call the created handler

	
handler
(
&
writer
,
nil
)

writer should be used to response with error

	
assert
.
LessOrEqual
(
t
,
1
,
writer
.
headerCalls
)
assert
.
LessOrEqual
(
t
,
1
,
writer
.
writeCalls
)
assert
.
LessOrEqual
(
t
,
1
,
writer
.
writeHeaderCalls
)
}

TestCreateAPIHandlerPathToExistingFile test the function CreateOpenAPIHandler when regular file name is provided

func
TestCreateAPIHandlerPathToExistingFile
(
t
*
testing
.
T
)
{

that file should exists everywhere

	
handler
:=
httputils
.
CreateOpenAPIHandler
(
"/etc/passwd"
,
true
,
false
)

use mock instead of real http.ResponseWriter struct

	
writer
:=
NewResponseWriterMock
(
false
)

try to call the created handler

	
handler
(
&
writer
,
nil
)

writer should be used

	
assert
.
Equal
(
t
,
1
,
writer
.
headerCalls
)
assert
.
Equal
(
t
,
1
,
writer
.
writeCalls
)
assert
.
Equal
(
t
,
0
,
writer
.
writeHeaderCalls
)
}

TestCreateAPIHandlerWriteError test the function CreateOpenAPIHandler when regular file name is provided and writer throws error on Write()

func
TestCreateAPIHandlerWriteError
(
t
*
testing
.
T
)
{

that file should exists everywhere

	
handler
:=
httputils
.
CreateOpenAPIHandler
(
"/etc/passwd"
,
true
,
false
)

use mock instead of real http.ResponseWriter struct

	
writer
:=
NewResponseWriterMock
(
true
)

try to call the created handler

	
handler
(
&
writer
,
nil
)

writer should be used

	
assert
.
LessOrEqual
(
t
,
1
,
writer
.
headerCalls
)
assert
.
LessOrEqual
(
t
,
1
,
writer
.
writeCalls
)
assert
.
LessOrEqual
(
t
,
1
,
writer
.
writeHeaderCalls
)
}

TestCreateAPIHandlerPathToExistingFileNoDebugMode test the function CreateOpenAPIHandler when regular file name is provided and debug mode is disabled

func
TestCreateAPIHandlerPathToExistingFileNoDebugMode
(
t
*
testing
.
T
)
{

that file should exists everywhere

	
handler
:=
httputils
.
CreateOpenAPIHandler
(
"/etc/passwd"
,
false
,
false
)

use mock instead of real http.ResponseWriter struct

	
writer
:=
NewResponseWriterMock
(
false
)

try to call the created handler

	
handler
(
&
writer
,
nil
)

writer should be used

	
assert
.
Equal
(
t
,
1
,
writer
.
headerCalls
)
assert
.
Equal
(
t
,
1
,
writer
.
writeCalls
)
assert
.
Equal
(
t
,
0
,
writer
.
writeHeaderCalls
)
}

TestCreateAPIHandlerPathToExistingJSONFile test the function CreateOpenAPIHandler when regular JSON file name is provided and debug mode is disabled

func
TestCreateAPIHandlerPathToExistingJSONFile
(
t
*
testing
.
T
)
{

temporary file with OpenAPI.json content

	
tempFile
,
err
:=
ioutil
.
TempFile
(
""
,
"test_openapi.json"
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}

close and remove the temporary file at the end of the test

	
defer
func
(
)
{
err
:=
tempFile
.
Close
(
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
(
)
defer
func
(
)
{
err
:=
os
.
Remove
(
tempFile
.
Name
(
)
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
(
)

write data to the temporary file

	
data
:=
[
]
byte
(
openAPIFile
)
if
_
,
err
:=
tempFile
.
Write
(
data
)
;
err
!=
nil
{
t
.
Fatal
(
err
)
}

that file should now exists

	
handler
:=
httputils
.
CreateOpenAPIHandler
(
tempFile
.
Name
(
)
,
false
,
false
)

use mock instead of real http.ResponseWriter struct

	
writer
:=
NewResponseWriterMock
(
false
)

try to call the created handler

	
handler
(
&
writer
,
nil
)

writer should be used

	
assert
.
Equal
(
t
,
1
,
writer
.
headerCalls
)
assert
.
Equal
(
t
,
1
,
writer
.
writeCalls
)
assert
.
Equal
(
t
,
0
,
writer
.
writeHeaderCalls
)
}