From 5d5d657192c00b2f16c5ff59942dd98c1d9b8d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20Sch=C3=BCrmann?= Date: Mon, 21 Mar 2022 11:47:17 +0100 Subject: [PATCH] Add: Initial commit --- .gitignore | 129 ++++++++++++++++++++++++++ LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++ Pipfile | 17 ++++ README.md | 78 ++++++++++++++++ daxsp.csv | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 90 ++++++++++++++++++ test_main.py | 10 ++ 7 files changed, 782 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Pipfile create mode 100644 README.md create mode 100644 daxsp.csv create mode 100644 main.py create mode 100644 test_main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eda621e --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +Pipfile.lock +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..c23cb73 --- /dev/null +++ b/Pipfile @@ -0,0 +1,17 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +fastapi = "*" +uvicorn = "*" +python-multipart = "*" + +[dev-packages] +pytest = "*" +requests = "*" +autopep8 = "*" + +[requires] +python_version = "3.6" diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfac00d --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ + +# IDA recruitment test + +IDA (**I**n**D**ex re**A**der) is an API for providing data from the well-known share indices ([DAX](https://en.wikipedia.org/wiki/DAX) & [S&P](https://en.wikipedia.org/wiki/S%26P_Global_Ratings)). It is intended to represent an exemplary application that exists within [Union Investment](https://www.union-investment.de/startseite) and is designed to test the basic programming skills of applicants. + +Technically, [Python 3](https://www.python.org/) and [FastAPI](https://fastapi.tiangolo.com/) are used for the API. Both technologies allow fast and stable web service development. + + +## Table of Contents + +- [Preparation](#preparation) +- [Exercises](#exercises) + - [Excercise 1](#excercise-1) + - [Excercise 2](#excercise-2) + - [Excercise 3](#excercise-3) + +## Preparation + +Before you start, you must: + +- Install [Python 3.6.x.](https://www.python.org/downloads/) +- Install [Pipenv](https://pipenv.pypa.io/en/latest/#install-pipenv-today) + +To activate this project's virtualenv, run following command: + +```python +pipenv shell +``` + +Then install the project dependencies with: + +```python +pipenv install +``` + +After successful installation the API can be started locally with the command: + +```python +uvicorn main:app --reload +``` + +You will now be able of accessing the API via your browser on [http://127.0.0.1:8000](http://127.0.0.1:8000). + +## Exercises + +Please complete the exercises within one week and submit the results as previously agreed.We will discuss the task together in a subsequent interview. All tasks can be solved with the help of the [documentation](https://fastapi.tiangolo.com/). + +If you have direct questions about the Recrutiment Test, you can contact us directly via e-mail at [cloudoperations@union-investment.de](mailto:cloudoperations@union-investment.de). + +**Good luck with the exercises!** + +### Excercise 1 + +Extend the API with another endpoint that should be accessible via the `/getdata` path. This endpoint is to return all share indices in a list page by page. Each page should contain a maximum of 30 elements. + +### Excercise 2 + +Please complete the `create_upload_files` method so that you can update share indices data at runtime. The data should be sent in the form of a CSV file using an HTTP POST request. + +For example, the following curl command can be used for testing: + +```bash +curl -d @daxsp.csv http://127.0.0.1:8000/uploadfile +``` + +[Note in the documentation](https://fastapi.tiangolo.com/tutorial/request-files/) + +### Excercise 3 + +Finally, please write appropriate unit tests for your two tasks using the [components provided by FastAPI](https://fastapi.tiangolo.com/tutorial/testing/). +To do this, expand the test_main.py file. The tests can be run with the following command: + +```python +pytest +``` + +--- +***Copyright © 2022 Union IT-Services GmbH. All rights reserved.*** diff --git a/daxsp.csv b/daxsp.csv new file mode 100644 index 0000000..1bd604e --- /dev/null +++ b/daxsp.csv @@ -0,0 +1,257 @@ +Date,DAX,SP500 +20211201,15263,4513 +20211130,15472,4567 +20211129,15100,4655 +20211126,15280,4594 +20211124,15257,4701 +20211123,15917,4690 +20211122,15878,4682 +20211119,15937,4697 +20211118,16115,4704 +20211117,16159,4688 +20211116,16221,4700 +20211115,16251,4682 +20211112,16247,4682 +20211111,16148,4649 +20211110,16094,4646 +20211109,16083,4685 +20211108,16067,4701 +20211105,16040,4697 +20211104,16046,4680 +20211103,16054,4660 +20211102,16029,4630 +20211101,15959,4613 +20211029,15954,4605 +20211028,15806,4596 +20211027,15688,4551 +20211026,15696,4574 +20211025,15705,4566 +20211022,15757,4544 +20211021,15599,4549 +20211020,15542,4536 +20211019,15472,4519 +20211018,15522,4486 +20211015,15515,4471 +20211014,15474,4438 +20211013,15587,4363 +20211012,15462,4350 +20211011,15249,4361 +20211008,15146,4391 +20211007,15199,4399 +20211006,15206,4363 +20211005,15250,4345 +20211004,14973,4300 +20211001,15194,4357 +20210930,15036,4307 +20210929,15156,4359 +20210928,15260,4352 +20210927,15365,4443 +20210924,15248,4455 +20210923,15573,4448 +20210922,15531,4395 +20210921,15643,4354 +20210920,15506,4357 +20210917,15348,4432 +20210916,15132,4473 +20210915,15490,4480 +20210914,15651,4443 +20210913,15616,4468 +20210910,15722,4458 +20210909,15701,4493 +20210908,15609,4514 +20210907,15623,4520 +20210903,15610,4535 +20210902,15843,4536 +20210901,15932,4524 +20210831,15781,4522 +20210830,15840,4528 +20210827,15824,4509 +20210826,15835,4470 +20210825,15887,4496 +20210824,15851,4486 +20210823,15793,4479 +20210820,15860,4441 +20210819,15905,4405 +20210818,15852,4400 +20210817,15808,4448 +20210816,15765,4479 +20210813,15965,4468 +20210812,15921,4460 +20210811,15925,4442 +20210810,15977,4436 +20210809,15937,4432 +20210806,15826,4436 +20210805,15770,4429 +20210804,15745,4402 +20210803,15761,4423 +20210802,15744,4387 +20210730,15692,4395 +20210729,15555,4419 +20210728,15568,4400 +20210727,15544,4401 +20210726,15640,4422 +20210723,15570,4411 +20210722,15519,4367 +20210721,15618,4358 +20210720,15669,4323 +20210719,15514,4258 +20210716,15422,4327 +20210715,15216,4360 +20210714,15133,4374 +20210713,15540,4369 +20210712,15629,4384 +20210709,15788,4369 +20210708,15789,4320 +20210707,15790,4358 +20210706,15687,4343 +20210702,15420,4352 +20210701,15692,4319 +20210630,15511,4297 +20210629,15661,4291 +20210628,15650,4290 +20210625,15603,4280 +20210624,15531,4266 +20210623,15690,4241 +20210622,15554,4246 +20210621,15607,4224 +20210618,15589,4166 +20210617,15456,4221 +20210616,15636,4223 +20210615,15603,4246 +20210614,15448,4255 +20210611,15727,4247 +20210610,15710,4239 +20210609,15729,4219 +20210608,15673,4227 +20210607,15693,4226 +20210604,15571,4229 +20210603,15581,4192 +20210602,15640,4208 +20210601,15677,4202 +20210528,15692,4204 +20210527,15632,4200 +20210526,15602,4195 +20210525,15567,4188 +20210524,15421,4197 +20210521,15519,4155 +20210520,15406,4159 +20210519,15450,4115 +20210518,15465,4127 +20210517,15437,4163 +20210514,15370,4173 +20210513,15113,4112 +20210512,15386,4063 +20210511,15396,4152 +20210510,15416,4188 +20210507,15199,4232 +20210506,15150,4201 +20210505,15119,4167 +20210504,15400,4164 +20210503,15399,4192 +20210430,15196,4181 +20210429,15170,4211 +20210428,14856,4183 +20210427,15236,4186 +20210426,15135,4187 +20210423,15154,4180 +20210422,15292,4134 +20210421,15249,4173 +20210420,15296,4134 +20210419,15279,4163 +20210416,15320,4185 +20210415,15195,4170 +20210414,15129,4124 +20210413,15368,4141 +20210412,15459,4127 +20210409,15255,4128 +20210408,15209,4097 +20210407,15234,4079 +20210406,15215,4073 +20210405,15234,4077 +20210401,15202,4019 +20210331,15176,3972 +20210330,15212,3958 +20210329,15107,3971 +20210326,15008,3974 +20210325,15008,3909 +20210324,14817,3889 +20210323,14748,3910 +20210322,14621,3940 +20210319,14610,3913 +20210318,14662,3915 +20210317,14657,3974 +20210316,14621,3962 +20210315,14775,3968 +20210312,14596,3943 +20210311,14557,3939 +20210310,14461,3898 +20210309,14502,3875 +20210308,14569,3821 +20210305,14540,3841 +20210304,14437,3768 +20210303,14380,3819 +20210302,13920,3870 +20210301,14056,3901 +20210226,14080,3811 +20210225,14039,3829 +20210224,14012,3925 +20210223,13786,3881 +20210222,13879,3876 +20210219,13976,3906 +20210218,13864,3913 +20210217,13950,3931 +20210216,13993,3932 +20210212,13886,3934 +20210211,13909,3916 +20210210,14064,3909 +20210209,14109,3911 +20210208,14049,3915 +20210205,14040,3886 +20210204,13932,3871 +20210203,14011,3830 +20210202,14059,3826 +20210201,14056,3773 +20210129,14060,3714 +20210128,13933,3787 +20210127,13835,3750 +20210126,13622,3849 +20210125,13432,3855 +20210122,13665,3841 +20210121,13620,3853 +20210120,13870,3851 +20210119,13643,3798 +20210115,13873,3768 +20210114,13906,3795 +20210113,13921,3809 +20210112,13815,3801 +20210111,13848,3799 +20210108,13787,3824 +20210107,13988,3803 +20210106,13939,3748 +20210105,13925,3726 +20210104,13936,3700 +20201231,14049,3756 +20201230,13968,3732 +20201229,13891,3727 +20201228,13651,3735 +20201224,13726,3703 +20201223,13718,3690 +20201222,13761,3687 +20201221,13790,3694 +20201218,13587,3709 +20201217,13418,3722 +20201216,13246,3701 +20201215,13630,3694 +20201214,13667,3647 +20201211,13565,3663 +20201210,13362,3668 +20201209,13223,3672 +20201208,13114,3702 +20201207,13295,3691 +20201204,13340,3699 +20201203,13278,3666 +20201202,13271,3669 +20201202,13271,3669 +20201202,13271,3669 +20201202,13271,3669 +20201202,13271,3669 diff --git a/main.py b/main.py new file mode 100644 index 0000000..718c251 --- /dev/null +++ b/main.py @@ -0,0 +1,90 @@ +import uvicorn +from starlette.responses import RedirectResponse +from fastapi import FastAPI, HTTPException, File, UploadFile +from typing import Optional, List +from csv import reader + + +### gloabls +app = FastAPI() +indices = [] + +@app.get("/") +async def redirect(): + response = RedirectResponse(url='/docs') + return response + +@app.get("/getdata/{date}") +async def read_item(date: str, index: str = "DAX", show_all_indices: Optional[bool] = False): + result = {"date": date} + + if date not in fake_items_db: + raise HTTPException(status_code=404, detail="Date not found") + + elif show_all_indices: + for i in indices: + result.update({i: fake_items_db[date][i]}) + + else: + result.update({index: fake_items_db[date][index]}) + + return result + + + +@app.post("/uploadfile/") +async def create_upload_files(file: UploadFile = File(...)): + content = await file.read() + + # add the entries of the uploaded file to fake_items_db + # data structure of fake_items_db: + # {"09091990": {"DAX": "1232456", "SP500:" "9238748"}, "08081990":{"DAX": "2345646", "SP500:" "2324432"}, ..} + + + + + +def add_entries_to_dict(data): + """ + Adds the entries to the global fake_items_db dictionairy. + Only available during server runtime. + + Args: + data (list(list)): [["Date", "DAX", "SP500"], ["30112021", "132345", "762190"], ..] + """ + for line_no, line in enumerate(data, 0): + if (line_no == 0): + new_indices = line[1:] + for index in new_indices: + indices.append(index) if index not in indices else indices + #todo: check order of line of line_no == 0 and indices + else: + fake_items_db.update({line[0]: {}}) + for index_no, index in enumerate(indices, 1): + fake_items_db[line[0]].update({index: line[index_no + 0]}) + + +def initialize_dict(fake_items_db, filename): + """ + Initializes the fake_items_db dictionairy + + Args: + fake_items_db (dict): initial empty dictionairy + filename (string): path to inital file + + Returns: + dict(dict): {"09091990": {"DAX": "1232456", "SP500:" "9238748"}, "08081990":{"DAX": "2345646", "SP500:" "2324432"} ..} + """ + file = open('./' + filename, encoding='UTF8') + csv_reader = reader(file) + add_entries_to_dict(csv_reader) + file.close() + + return fake_items_db + + +fake_items_db = dict() +fake_items_db = initialize_dict(fake_items_db=fake_items_db, filename="daxsp.csv") + +if __name__ == "__main__": + uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False) diff --git a/test_main.py b/test_main.py new file mode 100644 index 0000000..fcd9c63 --- /dev/null +++ b/test_main.py @@ -0,0 +1,10 @@ +from fastapi.testclient import TestClient + +from main import app + +client = TestClient(app) + +def test_redirect(): + response = client.get("/") + assert response.status_code == 200 + assert response.text.find("Swagger") != -1