module Test.Swagger where
import qualified API.Brig as BrigP
import qualified Data.ByteString as B
import qualified Data.Set as Set
import Data.String.Conversions
import GHC.Stack
import System.Exit
import System.FilePath
import System.Process
import Testlib.Assertions
import Testlib.Prelude
import UnliftIO.Temporary
existingVersions :: Set Int
existingVersions :: Set Int
existingVersions = [Int] -> Set Int
forall a. Ord a => [a] -> Set a
Set.fromList [Int
0, Int
1, Int
2, Int
3, Int
4, Int
5, Int
6, Int
7]
internalApis :: Set String
internalApis :: Set String
internalApis = [String] -> Set String
forall a. Ord a => [a] -> Set a
Set.fromList [String
"brig", String
"cannon", String
"cargohold", String
"cannon", String
"spar"]
testSwagger :: (HasCallStack) => App ()
testSwagger :: HasCallStack => App ()
testSwagger = do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse App Response
HasCallStack => App Response
BrigP.getApiVersions ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Set Int
actualVersions :: Set Int <- do
Set Int
sup <- Response
resp.json App Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"supported" App Value -> (App Value -> App (Set Int)) -> App (Set Int)
forall a b. a -> (a -> b) -> b
& (Value -> App Int)
-> MakesValue (App Value) => App Value -> App (Set Int)
forall b a.
(HasCallStack, Ord b) =>
(Value -> App b) -> MakesValue a => a -> App (Set b)
asSetOf Value -> App Int
forall i a. (Integral i, HasCallStack, MakesValue a) => a -> App i
asIntegral
Set Int
dev <- Response
resp.json App Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"development" App Value -> (App Value -> App (Set Int)) -> App (Set Int)
forall a b. a -> (a -> b) -> b
& (Value -> App Int)
-> MakesValue (App Value) => App Value -> App (Set Int)
forall b a.
(HasCallStack, Ord b) =>
(Value -> App b) -> MakesValue a => a -> App (Set b)
asSetOf Value -> App Int
forall i a. (Integral i, HasCallStack, MakesValue a) => a -> App i
asIntegral
Set Int -> App (Set Int)
forall a. a -> App a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Set Int -> App (Set Int)) -> Set Int -> App (Set Int)
forall a b. (a -> b) -> a -> b
$ Set Int
sup Set Int -> Set Int -> Set Int
forall a. Semigroup a => a -> a -> a
<> Set Int
dev
HasCallStack => String -> Bool -> App ()
String -> Bool -> App ()
assertBool (String
"unexpected actually existing versions: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Set Int -> String
forall a. Show a => a -> String
show Set Int
actualVersions)
(Bool -> App ()) -> Bool -> App ()
forall a b. (a -> b) -> a -> b
$
Set Int
actualVersions
Set Int -> Set Int -> Bool
forall a. Ord a => Set a -> Set a -> Bool
`Set.isSubsetOf` Set Int
existingVersions
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse App Response
HasCallStack => App Response
BrigP.getSwaggerPublicTOC ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
ByteString -> String
forall a b. ConvertibleStrings a b => a -> b
cs Response
resp.body HasCallStack => String -> String -> App ()
String -> String -> App ()
`shouldContainString` String
"<html>"
Set Int -> (Int -> App ()) -> App ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ Set Int
existingVersions ((Int -> App ()) -> App ()) -> (Int -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Int
v -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (HasCallStack => Int -> App Response
Int -> App Response
BrigP.getSwaggerPublicAllUI Int
v) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
ByteString -> String
forall a b. ConvertibleStrings a b => a -> b
cs Response
resp.body HasCallStack => String -> String -> App ()
String -> String -> App ()
`shouldContainString` String
"<!DOCTYPE html>"
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (HasCallStack => Int -> App Response
Int -> App Response
BrigP.getSwaggerPublicAllJson Int
v) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
App Value -> App ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void Response
resp.json
Set String -> (String -> App ()) -> App ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ Set String
internalApis ((String -> App ()) -> App ()) -> (String -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \String
api -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (HasCallStack => String -> App Response
String -> App Response
BrigP.getSwaggerInternalUI String
api) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
ByteString -> String
forall a b. ConvertibleStrings a b => a -> b
cs Response
resp.body HasCallStack => String -> String -> App ()
String -> String -> App ()
`shouldContainString` String
"<!DOCTYPE html>"
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (HasCallStack => String -> App Response
String -> App Response
BrigP.getSwaggerInternalJson String
api) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
App Value -> App ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void Response
resp.json
testSwaggerInternalVersionedNotFound :: (HasCallStack) => App ()
testSwaggerInternalVersionedNotFound :: HasCallStack => App ()
testSwaggerInternalVersionedNotFound = do
Set String -> (String -> App ()) -> App ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ Set String
internalApis ((String -> App ()) -> App ()) -> (String -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \String
api -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (String -> App Response
getSwaggerInternalUI String
api) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
where
getSwaggerInternalUI :: String -> App Response
getSwaggerInternalUI :: String -> App Response
getSwaggerInternalUI String
srv =
Domain -> Service -> Versioned -> String -> App Request
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Service -> Versioned -> String -> App Request
rawBaseRequest Domain
OwnDomain Service
Brig (Int -> Versioned
ExplicitVersion Int
2) ([String] -> String
joinHttpPath [String
"api-internal", String
"swagger-ui", String
srv])
App Request -> (Request -> App Response) -> App Response
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> Request -> App Response
submit String
"GET"
testSwaggerToc :: (HasCallStack) => App ()
testSwaggerToc :: HasCallStack => App ()
testSwaggerToc = do
[String] -> (String -> App ()) -> App ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [String
"/api/swagger-ui", String
"/api/swagger-ui/index.html", String
"/api/swagger.json"] ((String -> App ()) -> App ()) -> (String -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \String
path ->
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (String -> App Response
get String
path) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
let body :: String
body = forall a b. ConvertibleStrings a b => a -> b
cs @_ @String Response
resp.body
String
body String -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
html
Set Int -> (Int -> App ()) -> App ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ Set Int
existingVersions ((Int -> App ()) -> App ()) -> (Int -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Int
v ->
String
body HasCallStack => String -> String -> App ()
String -> String -> App ()
`shouldContainString` (String
"v" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show Int
v)
where
get :: String -> App Response
get :: String -> App Response
get String
path = Domain -> Service -> Versioned -> String -> App Request
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Service -> Versioned -> String -> App Request
rawBaseRequest Domain
OwnDomain Service
Brig Versioned
Unversioned String
path App Request -> (Request -> App Response) -> App Response
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> Request -> App Response
submit String
"GET"
html :: String
html :: String
html = String
"<html><head></head><body><h1>OpenAPI 3.0 docs for all Wire APIs</h1>\n<p>This wire-server system provides <a href=\"https://swagger.io/resources/open-api/\">OpenAPI 3.0</a> documentation of our HTTP REST API.</p> <p>The openapi docs are correct by construction (compiled from the server code), and more or less complete.</p> <p>Some endpoints are version-controlled. </a href=\"/api-version\">Show all supported versions.</a> <a href=\"https://docs.wire.com/developer/developer/api-versioning.html\">find out more.</a>\n<h2>Public (all available versions)</h2>\nv0: \n<a href=\"/v0/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v0/api/swagger.json\">swagger.json</a>\n<br>\nv1: \n<a href=\"/v1/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v1/api/swagger.json\">swagger.json</a>\n<br>\nv2: \n<a href=\"/v2/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v2/api/swagger.json\">swagger.json</a>\n<br>\nv3: \n<a href=\"/v3/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v3/api/swagger.json\">swagger.json</a>\n<br>\nv4: \n<a href=\"/v4/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v4/api/swagger.json\">swagger.json</a>\n<br>\nv5: \n<a href=\"/v5/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v5/api/swagger.json\">swagger.json</a>\n<br>\nv6: \n<a href=\"/v6/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v6/api/swagger.json\">swagger.json</a>\n<br>\nv7: \n<a href=\"/v7/api/swagger-ui\">swagger-ui</a>; \n<a href=\"/v7/api/swagger.json\">swagger.json</a>\n<br>\n\n<h2>Internal (not versioned)</h2>\n<p>Openapi docs for internal endpoints are served per service. I.e. there's one for `brig`, one for `cannon`, etc.. This is because Openapi doesn't play well with multiple actions having the same combination of HTTP method and URL path.</p>\nbrig:<br>\n<a href=\"/api-internal/swagger-ui/brig\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/brig-swagger.json\">swagger.json</a>\n<br>\ngalley:<br>\n<a href=\"/api-internal/swagger-ui/galley\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/galley-swagger.json\">swagger.json</a>\n<br>\nspar:<br>\n<a href=\"/api-internal/swagger-ui/spar\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/spar-swagger.json\">swagger.json</a>\n<br>\ncargohold:<br>\n<a href=\"/api-internal/swagger-ui/cargohold\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/cargohold-swagger.json\">swagger.json</a>\n<br>\ngundeck:<br>\n<a href=\"/api-internal/swagger-ui/gundeck\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/gundeck-swagger.json\">swagger.json</a>\n<br>\ncannon:<br>\n<a href=\"/api-internal/swagger-ui/cannon\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/cannon-swagger.json\">swagger.json</a>\n<br>\nproxy:<br>\n<a href=\"/api-internal/swagger-ui/proxy\">swagger-ui</a>; \n<a href=\"/api-internal/swagger-ui/proxy-swagger.json\">swagger.json</a>\n<br>\n\n<h2>Federated API (backend-to-backend)</h2>\nbrig (v0):<br><a href=\"/v0/api-federation/swagger-ui/brig\">swagger-ui</a>; <a href=\"/v0/api-federation/swagger-ui/brig-swagger.json\">swagger.json</a><br>brig (v1):<br><a href=\"/v1/api-federation/swagger-ui/brig\">swagger-ui</a>; <a href=\"/v1/api-federation/swagger-ui/brig-swagger.json\">swagger.json</a><br>brig (v2):<br><a href=\"/v2/api-federation/swagger-ui/brig\">swagger-ui</a>; <a href=\"/v2/api-federation/swagger-ui/brig-swagger.json\">swagger.json</a><br><br>\ngalley (v0):<br><a href=\"/v0/api-federation/swagger-ui/galley\">swagger-ui</a>; <a href=\"/v0/api-federation/swagger-ui/galley-swagger.json\">swagger.json</a><br>galley (v1):<br><a href=\"/v1/api-federation/swagger-ui/galley\">swagger-ui</a>; <a href=\"/v1/api-federation/swagger-ui/galley-swagger.json\">swagger.json</a><br>galley (v2):<br><a href=\"/v2/api-federation/swagger-ui/galley\">swagger-ui</a>; <a href=\"/v2/api-federation/swagger-ui/galley-swagger.json\">swagger.json</a><br><br>\ncargohold (v0):<br><a href=\"/v0/api-federation/swagger-ui/cargohold\">swagger-ui</a>; <a href=\"/v0/api-federation/swagger-ui/cargohold-swagger.json\">swagger.json</a><br>cargohold (v1):<br><a href=\"/v1/api-federation/swagger-ui/cargohold\">swagger-ui</a>; <a href=\"/v1/api-federation/swagger-ui/cargohold-swagger.json\">swagger.json</a><br>cargohold (v2):<br><a href=\"/v2/api-federation/swagger-ui/cargohold\">swagger-ui</a>; <a href=\"/v2/api-federation/swagger-ui/cargohold-swagger.json\">swagger.json</a><br><br>\n\n</body></html>\n"
data Swagger = SwaggerPublic | SwaggerInternal Service
instance TestCases Swagger where
mkTestCases :: IO [TestCase Swagger]
mkTestCases =
[TestCase Swagger] -> IO [TestCase Swagger]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
[ String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=ibrig]" (Service -> Swagger
SwaggerInternal Service
Brig),
String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=icannon]" (Service -> Swagger
SwaggerInternal Service
Cannon),
String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=icargohold]" (Service -> Swagger
SwaggerInternal Service
Cargohold),
String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=igalley]" (Service -> Swagger
SwaggerInternal Service
Galley),
String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=igundeck]" (Service -> Swagger
SwaggerInternal Service
Gundeck),
String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=ispar]" (Service -> Swagger
SwaggerInternal Service
Spar),
String -> Swagger -> TestCase Swagger
forall a. String -> a -> TestCase a
MkTestCase String
"[swagger=public]" Swagger
SwaggerPublic
]
testSwaggerLint :: (HasCallStack) => Swagger -> App ()
testSwaggerLint :: HasCallStack => Swagger -> App ()
testSwaggerLint Swagger
sw = do
String -> (String -> App ()) -> App ()
forall (m :: * -> *) a.
MonadUnliftIO m =>
String -> (String -> m a) -> m a
withSystemTempDirectory String
"swagger" ((String -> App ()) -> App ()) -> (String -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \String
tmp -> do
Request
req <- case Swagger
sw of
Swagger
SwaggerPublic ->
Domain -> Service -> Versioned -> String -> App Request
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Service -> Versioned -> String -> App Request
baseRequest Domain
OwnDomain Service
Brig Versioned
Versioned
(String -> App Request) -> String -> App Request
forall a b. (a -> b) -> a -> b
$ [String] -> String
joinHttpPath [String
"api", String
"swagger.json"]
(SwaggerInternal Service
service) ->
Domain -> Service -> Versioned -> String -> App Request
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Service -> Versioned -> String -> App Request
baseRequest Domain
OwnDomain Service
Brig Versioned
Unversioned
(String -> App Request) -> String -> App Request
forall a b. (a -> b) -> a -> b
$ [String] -> String
joinHttpPath
[ String
"api-internal",
String
"swagger-ui",
Service -> String
serviceName Service
service String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"-swagger.json"
]
ByteString
swagger <- String -> Request -> App Response
submit String
"GET" Request
req App Response -> (Response -> App ByteString) -> App ByteString
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Int -> Response -> App ByteString
Int -> Response -> App ByteString
getBody Int
200
IO () -> App ()
forall a. IO a -> App a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> App ()) -> IO () -> App ()
forall a b. (a -> b) -> a -> b
$ String -> ByteString -> IO ()
B.writeFile (String
tmp String -> String -> String
</> String
"swagger.json") ByteString
swagger
let cmd :: CreateProcess
cmd = String -> CreateProcess
shell (String -> CreateProcess) -> String -> CreateProcess
forall a b. (a -> b) -> a -> b
$ String
"vacuum lint -a -d -e " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> (String
tmp String -> String -> String
</> String
"swagger.json")
(ExitCode
exitCode, String
out, String
err) <- IO (ExitCode, String, String) -> App (ExitCode, String, String)
forall a. IO a -> App a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ExitCode, String, String) -> App (ExitCode, String, String))
-> IO (ExitCode, String, String) -> App (ExitCode, String, String)
forall a b. (a -> b) -> a -> b
$ CreateProcess -> String -> IO (ExitCode, String, String)
readCreateProcessWithExitCode CreateProcess
cmd String
""
case ExitCode
exitCode of
ExitCode
ExitSuccess -> () -> App ()
forall a. a -> App a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
ExitCode
_ -> do
IO () -> App ()
forall a. IO a -> App a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> App ()) -> IO () -> App ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
forall (m :: * -> *). MonadIO m => String -> m ()
putStrLn String
out
IO () -> App ()
forall a. IO a -> App a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> App ()) -> IO () -> App ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
forall (m :: * -> *). MonadIO m => String -> m ()
putStrLn String
err
String -> App ()
forall a. HasCallStack => String -> App a
assertFailure String
"swagger validation errors"