/ tests / test_httpserver.nim
test_httpserver.nim
  1  # nim-graphql
  2  # Copyright (c) 2021 Status Research & Development GmbH
  3  # Licensed under either of
  4  #  * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
  5  #  * MIT license ([LICENSE-MIT](LICENSE-MIT))
  6  # at your option.
  7  # This file may not be copied, modified, or distributed except according to
  8  # those terms.
  9  
 10  # curl -X POST -H Content-Type:application/graphql http://localhost:8547/graphql -d @data.json
 11  
 12  import
 13    std/[os, json],
 14    pkg/[toml_serialization, unittest2, chronos],
 15    ../graphql, ../graphql/[httpserver, httpclient],
 16    ./test_utils
 17  
 18  when defined(tls):
 19    import
 20      ./keys/keys
 21  
 22  type
 23    Unit = object
 24      name: string
 25      skip: bool
 26      error: string
 27      opName: string
 28      code: string
 29      result: string
 30  
 31    TestCase = object
 32      units: seq[Unit]
 33  
 34    Counter = ref object
 35      skip: int
 36      fail: int
 37      ok: int
 38  
 39  const
 40    caseFolder = "tests" / "serverclient"
 41    serverAddress = initTAddress("127.0.0.1:8547")
 42  
 43  proc createServer(serverAddress: TransportAddress, authHooks: seq[AuthHook] = @[]): GraphqlHttpServerRef =
 44    let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
 45    var ctx = GraphqlRef.new()
 46    ctx.initMockApi()
 47  
 48    const schema = """
 49      type Query {
 50        name: String
 51        color: Int
 52        tree: Tree
 53      }
 54  
 55      type Tree {
 56        name: String
 57        age: Int
 58        number: [Int!]
 59      }
 60    """
 61    let r = ctx.parseSchema(schema)
 62    if r.isErr:
 63      debugEcho r.error
 64      return
 65  
 66    when defined(tls):
 67      let res = GraphqlHttpServerRef.new(
 68        graphql = ctx,
 69        address = serverAddress,
 70        tlsPrivateKey = TLSPrivateKey.init(SecureKey),
 71        tlsCertificate = TLSCertificate.init(SecureCrt),
 72        socketFlags = socketFlags,
 73        authHooks = authHooks
 74      )
 75    else:
 76      let res = GraphqlHttpServerRef.new(
 77        graphql = ctx,
 78        address = serverAddress,
 79        socketFlags = socketFlags,
 80        authHooks = authHooks
 81      )
 82  
 83    if res.isErr():
 84      debugEcho res.error
 85      return
 86  
 87    res.get()
 88  
 89  proc setupClient(address: TransportAddress): GraphqlHttpClientRef =
 90    const
 91      secure = defined(tls)
 92  
 93    GraphqlHttpClientRef.new(address, secure = secure).get()
 94  
 95  proc runExecutor(client: GraphqlHttpClientRef, unit: Unit, testStatusIMPL: var TestStatus) =
 96    client.operationName(unit.opName)
 97    let res = waitFor client.sendRequest(unit.code)
 98    check res.isOk
 99    if res.isErr:
100      debugEcho res.error
101      return
102  
103    let clientResp = res.get()
104    check (clientResp.status == 200) == (unit.error.len == 0)
105  
106    let resp = decodeResponse(clientResp.response)
107    if not resp.errors.isNil:
108      if unit.error.len == 0:
109        debugEcho $resp.errors
110      check unit.error.len != 0
111      let node = parseJson(unit.error)
112      check $node == $resp.errors
113  
114    if not resp.data.isNil:
115      check unit.result.len != 0
116      let node = parseJson(unit.result)
117      check $node == $resp.data
118  
119  proc runSuite(client: GraphqlHttpClientRef, fileName: string, counter: Counter) =
120    let parts = splitFile(fileName)
121    let cases = Toml.loadFile(fileName, TestCase)
122    suite parts.name:
123      for x in cases.units:
124        let unit = x # prevent nim >= 1.6 cannot capture lent
125        test unit.name:
126          if unit.skip:
127            skip()
128            inc counter.skip
129          else:
130            client.runExecutor(unit, testStatusIMPL)
131            if testStatusIMPL == OK:
132              inc counter.ok
133            else:
134              inc counter.fail
135  
136  proc executeCases() =
137    var counter = Counter()
138    let server = createServer(serverAddress)
139    server.start()
140    var client = setupClient(serverAddress)
141  
142    for fileName in walkDirRec(caseFolder):
143      client.runSuite(fileName, counter)
144  
145    waitFor server.closeWait()
146    debugEcho counter[]
147  
148  proc testHooks() =
149    proc mockAuth(req: HttpRequestRef): Future[HttpResponseRef] {.
150            gcsafe, async: (raises: [CatchableError]).} =
151      if req.headers.getString("Auth-Token") == "Good Token":
152        return HttpResponseRef(nil)
153  
154      return await req.respond(Http401, "Unauthorized access")
155  
156    let server = createServer(serverAddress, @[AuthHook(mockAuth)])
157    server.start()
158  
159    const
160      query = """{ __type(name: "ID") { kind }}"""
161      headers = [
162  
163        ("Auth-Token", "Good Token")
164      ]
165  
166    suite "Test Hooks":
167      test "no auth token":
168        let client = setupClient(serverAddress)
169  
170        let res = waitFor client.sendRequest(query)
171        check res.isOk
172        if res.isErr:
173          debugEcho res.error
174          return
175  
176        let resp = res.get()
177        check resp.status == 401
178        check resp.reason == "Unauthorized"
179        check resp.response == "Unauthorized access"
180  
181      test "good auth token":
182        let client = setupClient(serverAddress)
183        let res = waitFor client.sendRequest(query, @headers)
184        check res.isOk
185        if res.isErr:
186          debugEcho res.error
187          return
188  
189        let resp = res.get()
190        check resp.status == 200
191        check resp.reason == "OK"
192        check resp.response == """{"data":{"__type":{"kind":"SCALAR"}}}"""
193  
194    waitFor server.closeWait()
195  
196  template mainModule() {.used.} =
197    proc main() =
198      let conf = getConfiguration()
199      if conf.testFile.len == 0:
200        executeCases()
201        testHooks()
202        return
203  
204      # disable unittest param handler
205      var counter = Counter()
206      let fileName = caseFolder / conf.testFile
207      var client = setupClient(serverAddress)
208      if conf.unit.len == 0:
209        let server = createServer(serverAddress)
210        server.start()
211        client.runSuite(fileName, counter)
212        waitFor server.closeWait()
213        echo counter[]
214        return
215  
216      let cases = Toml.loadFile(fileName, TestCase)
217      let server = createServer(serverAddress)
218      server.start()
219      for x in cases.units:
220        let unit = x # prevent nim >= 1.6 cannot capture lent
221        if unit.name != conf.unit:
222          continue
223        test unit.name:
224          client.runExecutor(unit, testStatusIMPL)
225      waitFor server.closeWait()
226  
227    var message: string
228    ## Processing command line arguments
229    if processArguments(message) != ConfigStatus.Success:
230      echo message
231      quit(QuitFailure)
232    else:
233      if len(message) > 0:
234        echo message
235        quit(QuitSuccess)
236    main()
237  
238  const
239    crashCondition = defined(windows) and defined(i386) and defined(release)
240  
241  when isMainModule:
242    when not crashCondition:
243      import
244        ../graphql/test_config
245      mainModule()
246  else:
247    when not crashCondition:
248      executeCases()
249      testHooks()