diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..98c7611 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Your configuration (please complete the following information):** + - OS: [e.g. iOS] +- Python version: [e.g. 3.7.0] + - Kosmorro version: [e.g. 1.0.0] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/event-type-proposal.md b/.github/ISSUE_TEMPLATE/event-type-proposal.md new file mode 100644 index 0000000..2184ff0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/event-type-proposal.md @@ -0,0 +1,20 @@ +--- +name: Event type proposal +about: Suggest a type of event you'd like to see implemented in Kosmorro +title: '' +labels: "enhancement, \U0001F320 events" +assignees: '' + +--- + +**Event type**: +**Involved objects (check the boxes)**: +- [ ] Sun +- [ ] Earth's moon +- [ ] Planets + +**Definition of the event:** + + + +**Other useful information** (implementation proposal, etc): diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6ad8ee4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +| Q | A +| -------------- | --- +| Bug fix? | yes/no +| New feature? | yes/no +| Related issues | Fix #... +| Has BC-break | yes/no +| License | GNU AGPL-v3 + +**Checklist:** + +- [ ] I have updated the manpages + + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..12bf2e6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 + +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 10 + target-branch: master + reviewers: + - Deuchnord + commit-message: + prefix: chore + include: scope diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..6c147c1 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,66 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master, features] + pull_request: + branches: [master, features] + schedule: + - cron: '0 8 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['python'] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..aa7a46e --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,20 @@ +name: Commit lint + +on: + push: + branches: [master, features] + pull_request: + branches: [master, features] + +jobs: + commitlint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@v2 + with: + helpURL: 'https://github.com/Kosmorro/kosmorro/blob/master/CONTRIBUTING.md#commiting' + diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..7151b2b --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,26 @@ +name: Python Lint + +on: + push: + branches: [master, features] + pull_request: + branches: [master, features] + +jobs: + pylint: + runs-on: ubuntu-latest + + name: PyLint + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + pip install --upgrade pip pipenv + pipenv sync -d + - name: Lint + run: | + pipenv run pylint kosmorro *.py kosmorrolib/*.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..52fb17a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,84 @@ +name: Release Application + +on: + push: + tags: ['v*'] + +jobs: + release: + name: Create release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup + run: | + sudo apt update + sudo apt install ruby + sudo gem install ronn + - name: Prepare release + id: prepare_release + run: | + changelog="$(git diff HEAD^ HEAD -- CHANGELOG.md | grep -E '\+[#*]' | sed 's/^+//')" + changelog="${changelog//$'%'/'%25'}" + changelog="${changelog//$'\n'/'%0A'}" + changelog="${changelog//$'\r'/'%0D'}" + echo "::set-output name=changelog::$changelog" + - name: Build locales + env: + POEDITOR_API_ACCESS: ${{ secrets.POEDITOR_API_ACCESS }} + run: | + make POEDITOR_API_ACCESS="${POEDITOR_API_ACCESS}" i18n + tar cf locales.tar.gz kosmorrolib/locales + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Version ${{ github.ref }} + draft: true + prerelease: false + body: | + ${{ steps.prepare_release.outputs.changelog }} + - name: Upload locales + id: upload-locales + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./locales.tar.gz + asset_name: locales.tar.gz + asset_content_type: application/x-tar + + + pip: + name: Release to PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.7' + - name: Setup environment + run: | + sudo apt update + sudo apt install ruby + sudo gem install ronn + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine skyfield numpy tabulate Babel requests + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + POEDITOR_API_ACCESS: ${{ secrets.POEDITOR_API_ACCESS }} + POEDITOR_PROJECT_ID: 306433 + run: | + make POEDITOR_API_ACCESS="${POEDITOR_API_ACCESS}" POEDITOR_PROJECT_ID="306433" build + twine upload dist/* + diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..1c3d234 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,46 @@ +name: Stale + +on: + schedule: + - cron: '0 12 * * *' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['python'] + + steps: + - name: Close Stale Issues + uses: actions/stale@v3 + with: + start-date: 2021-02-01 + + days-before-stale: 60 + days-before-issue-close: 7 + days-before-pr-close: 14 + + stale-label: stale + close-issue-label: wontfix + close-pr-label: wontmerge + + exempt-issue-label: 'confirmed,enhancement' + exempt-pr-label: 'on hold' + + stale-issue-message: > + This issue has not been active since a lot of time. If you think it is still valid, feel free to reply with a simple comment. + Without any update, I will close it in 7 days. + stale-pr-message: > + This pull request has not been active since a lot of time. If you're still working on it, please reply with a simple comment. + Without any update, I will close it in 14 days. + + close-issue-message: > + Closing this issue. + If you think it should be considered, feel free to exhume it. + close-pr-message: > + Closing this PR, since it has not got activity for a while. + If you're still working on it, feel free to reopen it. diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..4ef76c0 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,77 @@ +name: Unit tests + +on: + push: + branches: [master, features] + pull_request: + branches: [master, features] + +jobs: + legacy-unit-tests: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-18.04 + - ubuntu-20.04 + - macos-10.15 + - macos-11.0 + python-version: + - '3.7' + - '3.8' + - '3.9' + + name: Legacy unit tests (Python ${{ matrix.python-version }} on ${{ matrix.os }}) + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip pipenv + pipenv sync -d + - name: Unit tests + env: + COVERALLS_PRO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + make test + pipenv run coveralls --service=github + + unit-tests: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-18.04 + - ubuntu-20.04 + - macos-10.15 + - macos-11.0 + python-version: + - '3.7' + - '3.8' + - '3.9' + - '3.10' + + name: Unit tests (Python ${{ matrix.python-version }} on ${{ matrix.os }}) + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install --upgrade pip pipenv + pipenv sync -d + - name: Unit tests + env: + COVERALLS_PRO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + make test + pipenv run coveralls --service=github diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8a1bb0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.coverage diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8438cb2 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +.PHONY: test +tests: legacy-tests + python3 tests.py + +legacy-tests: + unset KOSMORRO_LATITUDE; \ + unset KOSMORRO_LONGITUDE; \ + unset KOSMORRO_TIMEZONE; \ + LANG=C pipenv run python3 -m coverage run -m unittest tests + +.PHONY: build +build: + python3 setup.py sdist bdist_wheel + +env: + @if [[ "$$RELEASE_NUMBER" == "" ]]; \ + then echo "Missing environment variable: RELEASE_NUMBER."; \ + echo 'Example: export RELEASE_NUMBER="1.0.0" (without the leading "v")'; \ + exit 1; \ + fi + +release: env + @echo -e "\e[1mCreating release with version number \e[36m$$RELEASE_NUMBER\e[0m" + @echo + + sed "s/^VERSION =.*/VERSION = '$$RELEASE_NUMBER'/g" kosmorrolib/version.py > version.py + mv version.py kosmorrolib/version.py + + pipenv run python setup.py extract_messages --output-file=kosmorrolib/locales/messages.pot > /dev/null + + conventional-changelog -p angular -i CHANGELOG.md -s + sed "0,/\\[\\]/s/\\[\\]/[v$$RELEASE_NUMBER]/g" CHANGELOG.md > /tmp/CHANGELOG.md + sed -e "s/...v)/...v$$RELEASE_NUMBER)/" /tmp/CHANGELOG.md > CHANGELOG.md + rm /tmp/CHANGELOG.md + + @echo + @echo -e "\e[1mRelease \e[36m$$RELEASE_NUMBER\e[39m is ready to commit." + @echo -e "Please review the changes, then invoke \e[33mmake finish-release\e[39m." + +finish-release: env + git add CHANGELOG.md kosmorrolib/version.py kosmorrolib/locales/messages.pot + git commit -m "build: bump version $$RELEASE_NUMBER" + git tag "v$$RELEASE_NUMBER" + git checkout features + git merge master + git checkout master + + @echo + @echo -e "\e[1mVersion \e[36m$$RELEASE_NUMBER\e[39m successfully tagged!" + @echo -e "Invoke \e[33mgit push origin master features v$$RELEASE_NUMBER\e[39m to finish." diff --git a/Pipfile b/Pipfile index 17a706b..8bd049e 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ pylint = "*" babel = "*" unittest-data-provider = "*" coveralls = "*" +parameterized = "*" [packages] skyfield = ">=1.32.0,<2.0.0" diff --git a/Pipfile.lock b/Pipfile.lock index 2267729..a08d36d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "05fc0ad542c6b81f02f9f1d6c4dbf8d2743ab07678c5c42aa3f7739c9aee7e36" + "sha256": "6b2c66b95f3dd68b2c892979187a16d4e60114c973470258e2cf0c091bf75b8c" }, "pipfile-spec": 6, "requires": { @@ -69,39 +69,43 @@ }, "sgp4": { "hashes": [ - "sha256:0864ba1062418b1b3e23e38e90032dd2c6be1f09b66c5026a18f8171548720a9", - "sha256:1601f590950cd34ffc54c8c2811a228632d394c1e28a74054373638f7ead4801", - "sha256:2a75446b7299217f7e66002677226dab4e41b1ad62ce7fb238c0947071655899", - "sha256:2ad7a1c6dde04591a847c85397151e2adb458d889838a9e10177055bf373ec4a", - "sha256:2c21fc1f47882136883390497f92afd387d79c35f23a534b652ae44e02da0518", - "sha256:318309f4efa05361a64498ec0ac8e42a288439efe9cce6f823366c792d645e5c", - "sha256:39acc3f6a5efb9ccf76e2d3fbe4b56c9c0de10db0d5d4cfdf421b0969bbaa1e6", - "sha256:41bcb23f9fe020fa41d1729eebdff97144a6c884260ce50065e9201c45a989f8", - "sha256:71db20e277b639c05287302521e7c56fd9f77d081bb7a5f251294c18af188e43", - "sha256:7ea354472687dd54a40c0199238e6d22e4b86ab4d5eb356353be8559816f2754", - "sha256:89b42139eefd741724b6e41b80510cdbd76b0bda8f85d6cb37564051d119c384", - "sha256:93edb356869cd0c4c8b715523d26555c69bf81cebad23e5a31abdd5623e8819b", - "sha256:967ac911e1ffbd795df97e89c17f0c07fb2c782ae16f02b2bed2f9ad834cc7f9", - "sha256:b59509b488c0674c29806c181238a4d4868797122fb8e6681f7a3e5540a25403", - "sha256:c76a8db1df422a8473a680fcfb3c6b4cfa0318cf0e7fac9ea1b898f898139b8f", - "sha256:d33c185606eeeb7067ab3626eccdfdc4ea74346093fae646541ce8998c861540", - "sha256:de3ddc1a8a01a8b9f67008d2da85376d59aebac464a54477e11f459119de5308", - "sha256:df87ca4b6eac69b7ee2a6c0f7a3a29d9eda9785e21767e320d768f14170dd693", - "sha256:e303c82a3fc51a73cf0f69ee3215b25c299d84fb766e522a71136ad6f9d3a6c0", - "sha256:e576d9f4721d6380d6a7b9a9ca4dbc863ba9bd6f9242b4df12c0f68b50282f45", - "sha256:f45d0a205fb18919ff83ca449d483e6306721365d5ea52d63f86d91b6ca967c0", - "sha256:f5e6787d59683bfa5c9e1b88210d7bafc36b2fd8799438f30e1effa7da76764c", - "sha256:f7fc5b55497d79fe638fe203bc634332cea3f466d3f08910a956f97477cdecbb", - "sha256:fec4b597c4cc3dd330c9fd049c2d88ccea16aac474a57343082fa17fe1f84a05" + "sha256:0bcc2e078d04a32f0db1278ecf434b85d51682ab0d6144490940f8a7479305c9", + "sha256:15745e778634c7aea8a7d53f0108562ee80d3f9f4e2812ab1884b20f5fb1f995", + "sha256:19cd0d8e2c0826819bd8e46ffcf7866d137078b4110603c644a9eb3427347fe6", + "sha256:2cb573d4831db7ebfcd80442ba72cd3cbcae99d33bcc8ac298186bb358515cad", + "sha256:465dc1ef603aac17f59a1b00716c1f0187b90eaf88ce14ff6b19a40c7dd4ad97", + "sha256:6a7b9c109664284df3914e71bc9f773eda574d920763de5aac882e4e02002730", + "sha256:786b8e674d23c9efa634b782dc9650970e8662c92bc7ba457296fad84afaf34a", + "sha256:7b638875f7072926e57145537e831128a9bc6fed54c52fac8f5248fb775acc3e", + "sha256:802db53c0bb3caf58f69e38fbb04d7648c98ec4b34e17b4c3e9f81308352a9e1", + "sha256:850e16475a13cb11ec12c69c9b91d03e8f61461b083d9c6232d6119c2777a13f", + "sha256:85fb39c28741270e1552bc90269e1d73d65eb1383696848100dd991622a87bf7", + "sha256:8777dc09d027b4bd74675c91cf2284d79f800e11a22d0e18acca1611c49ae955", + "sha256:968760092038672e6e0686b581090016d491bb373a41416d2898f334bfa831e8", + "sha256:96b8359c520e0e66d6d9ec0d775c948401887b8b60ee0775d3b6ad465f13ba6a", + "sha256:9a097c1f7f777bd3eb36578cc195253053794396e34e34d67e7e2c7fb6960fda", + "sha256:9e6b0a476a9481aec19a23d69292b0009a465962decc15d22407a99aa4ad306a", + "sha256:a427bb73931d91dcc5e8b8ca796636b862f172c471ebc000e43eed6d84e2ee72", + "sha256:ae4d2401eff33a31737ec15a947a97bebe784701e064bb1d3096e05b068ca5b8", + "sha256:b110b2e4cb9223913e48e1f1ed5783d52a29e97556fa89dfdef721d23d42b134", + "sha256:bc17cc051246e21b0e63a536d236ddec6e27761107c7c3acfb6cdfe6c61627d8", + "sha256:bc443f34a0a3d6c3b02701403c0cffc9af4a3e864f41168cd0fc6b416e27ff58", + "sha256:c13f98a9227fb5a242c9ea17bcc701e220deb75c576d240581107d4e9d20ce80", + "sha256:c3a17700f19c881cb816bc7998ccb32e07ad5dc1672affff90324e09f3262025", + "sha256:c600da1a26257b733df9de5e8a916f06160b21f819f073cd56cbe7f3be709877", + "sha256:cb07c686b2f5552f8048ad3cace371dac1e1a530fb1ce20031d1ef46854793c1", + "sha256:cff9b03cf770753522face8be7fec52d4ba9eee0fe9d73d324fa0724343a97a5", + "sha256:d31cd0347ddd3bbeee2c47867f0c8a8f9ed0d94815f902aa62aab676c740526b", + "sha256:dafc9bf8d52d13d82490b27b1d93a7189e408e1b7b83a838db4f39b21718053e" ], - "version": "==2.17" + "version": "==2.18" }, "six": { "hashes": [ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "skyfield": { @@ -115,11 +119,11 @@ "develop": { "astroid": { "hashes": [ - "sha256:87ae7f2398b8a0ae5638ddecf9987f081b756e0e9fc071aeebdca525671fc4dc", - "sha256:b31c92f545517dcc452f284bc9c044050862fbe6d93d2b3de4a215a6b384bf0d" + "sha256:21d735aab248253531bb0f1e1e6d068f0ee23533e18ae8a6171ff892b98297cf", + "sha256:cfc35498ee64017be059ceffab0a25bedf7548ab76f2bea691c5565896e7128d" ], "markers": "python_version >= '3.6'", - "version": "==2.5.0" + "version": "==2.5.1" }, "babel": { "hashes": [ @@ -146,66 +150,69 @@ }, "coverage": { "hashes": [ - "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7", - "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5", - "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f", - "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde", - "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f", - "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f", - "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c", - "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66", - "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90", - "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337", - "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d", - "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4", - "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409", - "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37", - "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1", - "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247", - "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39", - "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c", - "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994", - "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c", - "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb", - "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc", - "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f", - "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca", - "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135", - "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3", - "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339", - "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9", - "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9", - "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af", - "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370", - "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19", - "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3", - "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44", - "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3", - "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a", - "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c", - "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b", - "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9", - "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8", - "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22", - "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f", - "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345", - "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880", - "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0", - "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b", - "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec", - "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3", - "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786" + "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", + "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", + "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", + "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", + "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", + "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", + "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", + "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", + "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", + "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", + "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", + "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", + "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", + "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", + "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", + "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", + "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", + "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", + "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", + "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", + "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", + "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", + "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", + "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", + "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", + "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", + "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", + "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", + "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", + "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", + "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", + "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", + "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", + "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", + "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", + "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", + "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", + "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", + "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", + "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", + "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", + "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", + "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", + "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", + "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", + "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", + "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", + "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", + "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", + "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", + "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", + "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==5.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", + "version": "==5.5" }, "coveralls": { "hashes": [ - "sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434", - "sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f" + "sha256:7bd173b3425733661ba3063c88f180127cc2b20e9740686f86d2622b31b41385", + "sha256:cbb942ae5ef3d2b55388cb5b43e93a269544911535f1e750e1c656aef019ce60" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.0.1" }, "docopt": { "hashes": [ @@ -226,7 +233,7 @@ "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e", "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc" ], - "markers": "python_version >= '3.6' and python_version < '4'", + "markers": "python_version >= '3.6' and python_version < '4.0'", "version": "==5.7.0" }, "lazy-object-proxy": { @@ -266,13 +273,21 @@ ], "version": "==0.6.1" }, + "parameterized": { + "hashes": [ + "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c", + "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9" + ], + "index": "pypi", + "version": "==0.8.1" + }, "pylint": { "hashes": [ - "sha256:81ce108f6342421169ea039ff1f528208c99d2e5a9c4ca95cfc5291be6dfd982", - "sha256:a251b238db462b71d25948f940568bb5b3ae0e37dbaa05e10523f54f83e6cc7e" + "sha256:0e21d3b80b96740909d77206d741aa3ce0b06b41be375d92e1f3244a274c1f8a", + "sha256:d09b0b07ba06bcdff463958f53f23df25e740ecd81895f7d2699ec04bbd8dc3b" ], "index": "pypi", - "version": "==2.7.1" + "version": "==2.7.2" }, "pylintfileheader": { "hashes": [ @@ -302,7 +317,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "unittest-data-provider": { @@ -317,7 +332,7 @@ "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'", "version": "==1.26.3" }, "wrapt": { diff --git a/kosmorrolib/ephemerides.py b/kosmorrolib/ephemerides.py index 08c34b0..2ddc578 100644 --- a/kosmorrolib/ephemerides.py +++ b/kosmorrolib/ephemerides.py @@ -33,7 +33,7 @@ from .exceptions import OutOfRangeDateError RISEN_ANGLE = -0.8333 -def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhaseType, None]: +def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonPhase, None]: tomorrow = get_timescale().utc(now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1) phases = list(MoonPhaseType) @@ -65,12 +65,12 @@ def _get_skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[ next_phase_time = times[j] break - return MoonPhaseType(current_phase, + return MoonPhase(current_phase, current_phase_time.utc_datetime() if current_phase_time is not None else None, next_phase_time.utc_datetime() if next_phase_time is not None else None) -def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhaseType: +def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase: earth = get_skf_objects()['earth'] moon = get_skf_objects()['moon'] sun = get_skf_objects()['sun'] diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index 7aa5657..5d4081a 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -181,7 +181,7 @@ def get_events(date: date_type, timezone: int = 0) -> [Event]: """Calculate and return a list of events for the given date, adjusted to the given timezone if any. Find events that happen on April 4th, 2020 (show hours in UTC): - + >>> get_events(date_type(2020, 4, 4)) [, ] start=2020-04-04 01:14:39.063308+00:00 end=None details=None />] diff --git a/tests.py b/tests.py index 5ed410f..11e17a8 100644 --- a/tests.py +++ b/tests.py @@ -3,4 +3,4 @@ import doctest from kosmorrolib import * for module in [events]: - doctest.testmod(module) + doctest.testmod(module, verbose=True) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..01438bc --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,6 @@ +from .core import * +from .data import * +from .ephemerides import * +from .events import * +from .testutils import * +from .dateutil import * diff --git a/tests/core.py b/tests/core.py new file mode 100644 index 0000000..34cf8e5 --- /dev/null +++ b/tests/core.py @@ -0,0 +1,39 @@ +import unittest + +import os +import kosmorrolib.core as core + +from datetime import date +from dateutil.relativedelta import relativedelta + + +class CoreTestCase(unittest.TestCase): + def test_flatten_list(self): + self.assertEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], core.flatten_list([0, 1, 2, [3, 4, [5, 6], 7], 8, [9]])) + + def test_get_env(self): + self.assertEqual(0, len(core.get_env())) + + os.environ['SOME_RANDOM_VAR'] = 'an awesome value' + self.assertEqual(0, len(core.get_env())) + + os.environ['KOSMORRO_GREAT_VARIABLE'] = 'value' + env = core.get_env() + self.assertEqual(1, len(env)) + self.assertEqual('value', env.great_variable) + + os.environ['KOSMORRO_ANOTHER_VARIABLE'] = 'another value' + env = core.get_env() + self.assertEqual(2, len(env)) + self.assertEqual('value', env.great_variable) + self.assertEqual('another value', env.another_variable) + + self.assertEqual("{'great_variable': 'value', 'another_variable': 'another value'}", str(env)) + + def test_date_arg_parsing(self): + self.assertEqual(core.get_date("+1y 2m3d"), date.today() + relativedelta(years=1, months=2, days=3)) + self.assertEqual(core.get_date("-1y2d"), date.today() - relativedelta(years=1, days=2)) + self.assertEqual(core.get_date("1111-11-13"), date(1111, 11, 13)) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/data.py b/tests/data.py new file mode 100644 index 0000000..d1e2103 --- /dev/null +++ b/tests/data.py @@ -0,0 +1,17 @@ +import unittest + +from kosmorrolib import data, core + + +class DataTestCase(unittest.TestCase): + def test_object_radius_must_be_set_to_get_apparent_radius(self): + o = data.Planet('Saturn', 'SATURN') + + with self.assertRaises(ValueError) as context: + o.get_apparent_radius(core.get_timescale().now(), core.get_skf_objects()['earth']) + + self.assertEqual(('Missing radius for Saturn object',), context.exception.args) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/dateutil.py b/tests/dateutil.py new file mode 100644 index 0000000..984add3 --- /dev/null +++ b/tests/dateutil.py @@ -0,0 +1,24 @@ +import unittest + +from kosmorrolib import dateutil +from datetime import datetime + + +class DateUtilTestCase(unittest.TestCase): + def test_translate_to_timezone(self): + date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 4), to_tz=-2) + self.assertEqual(2, date.hour) + + date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 0), to_tz=2) + self.assertEqual(2, date.hour) + + date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 8), to_tz=2, from_tz=6) + self.assertEqual(4, date.hour) + + date = dateutil.translate_to_timezone(datetime(2020, 6, 9, 1), to_tz=0, from_tz=2) + self.assertEqual(8, date.day) + self.assertEqual(23, date.hour) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/ephemerides.py b/tests/ephemerides.py new file mode 100644 index 0000000..713a115 --- /dev/null +++ b/tests/ephemerides.py @@ -0,0 +1,124 @@ +import unittest + +from kosmorrolib.enum import MoonPhaseType + +from .testutils import expect_assertions +from kosmorrolib import ephemerides +from kosmorrolib.data import EARTH, Position, MoonPhase +from datetime import date +from kosmorrolib.exceptions import OutOfRangeDateError + + +class EphemeridesTestCase(unittest.TestCase): + def test_get_ephemerides_for_aster_returns_correct_hours(self): + position = Position(0, 0, EARTH) + eph = ephemerides.get_ephemerides(date=date(2019, 11, 18), + position=position) + + @expect_assertions(self.assertRegex, num=3) + def do_assertions(assert_regex): + for ephemeris in eph: + if ephemeris.object.skyfield_name == 'SUN': + assert_regex(ephemeris.rise_time.isoformat(), '^2019-11-18T05:41:') + assert_regex(ephemeris.culmination_time.isoformat(), '^2019-11-18T11:45:') + assert_regex(ephemeris.set_time.isoformat(), '^2019-11-18T17:48:') + break + + do_assertions() + + ################################################################################################################### + ### MOON PHASE TESTS ### + ################################################################################################################### + + def test_moon_phase_new_moon(self): + phase = ephemerides.get_moon_phase(date(2019, 11, 25)) + self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 26)) + self.assertEqual(MoonPhaseType.NEW_MOON, phase.phase_type) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 27)) + self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-12-04T') + + def test_moon_phase_first_crescent(self): + phase = ephemerides.get_moon_phase(date(2019, 11, 3)) + self.assertEqual(MoonPhaseType.WAXING_CRESCENT, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-04T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 4)) + self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.phase_type) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 5)) + self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') + + def test_moon_phase_full_moon(self): + phase = ephemerides.get_moon_phase(date(2019, 11, 11)) + self.assertEqual(MoonPhaseType.WAXING_GIBBOUS, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-12T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 12)) + self.assertEqual(MoonPhaseType.FULL_MOON, phase.phase_type) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 13)) + self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') + + def test_moon_phase_last_quarter(self): + phase = ephemerides.get_moon_phase(date(2019, 11, 18)) + self.assertEqual(MoonPhaseType.WANING_GIBBOUS, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-19T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 19)) + self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.phase_type) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') + + phase = ephemerides.get_moon_phase(date(2019, 11, 20)) + self.assertEqual(MoonPhaseType.WANING_CRESCENT, phase.phase_type) + self.assertIsNone(phase.time) + self.assertRegexpMatches(phase.next_phase_date.isoformat(), '^2019-11-26T') + + def test_moon_phase_prediction(self): + phase = MoonPhase(MoonPhaseType.NEW_MOON, None, None) + self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase()) + phase = MoonPhase(MoonPhaseType.WAXING_CRESCENT, None, None) + self.assertEqual(MoonPhaseType.FIRST_QUARTER, phase.get_next_phase()) + + phase = MoonPhase(MoonPhaseType.FIRST_QUARTER, None, None) + self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase()) + phase = MoonPhase(MoonPhaseType.WAXING_GIBBOUS, None, None) + self.assertEqual(MoonPhaseType.FULL_MOON, phase.get_next_phase()) + + phase = MoonPhase(MoonPhaseType.FULL_MOON, None, None) + self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase()) + phase = MoonPhase(MoonPhaseType.WANING_GIBBOUS, None, None) + self.assertEqual(MoonPhaseType.LAST_QUARTER, phase.get_next_phase()) + + phase = MoonPhase(MoonPhaseType.LAST_QUARTER, None, None) + self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase()) + phase = MoonPhase(MoonPhaseType.WANING_CRESCENT, None, None) + self.assertEqual(MoonPhaseType.NEW_MOON, phase.get_next_phase()) + + def test_get_ephemerides_raises_exception_on_out_of_date_range(self): + with self.assertRaises(OutOfRangeDateError): + ephemerides.get_ephemerides(date(1789, 5, 5), Position(0, 0, EARTH)) + + def test_get_moon_phase_raises_exception_on_out_of_date_range(self): + with self.assertRaises(OutOfRangeDateError): + ephemerides.get_moon_phase(date(1789, 5, 5)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/events.py b/tests/events.py new file mode 100644 index 0000000..dd92611 --- /dev/null +++ b/tests/events.py @@ -0,0 +1,83 @@ +import unittest + +from datetime import date, datetime +from parameterized import parameterized + +from kosmorrolib import events +from kosmorrolib.data import Event, ASTERS +from kosmorrolib.enum import EventType +from kosmorrolib.exceptions import OutOfRangeDateError + +EXPECTED_EVENTS = [ + (date(2020, 2, 7), []), + + (date(2020, 10, 13), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))]), + + (date(2022, 12, 8), + [Event(EventType.CONJUNCTION, [ASTERS[1], ASTERS[4]], datetime(2022, 12, 8, 4, 18)), + Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2022, 12, 8, 5, 41))]), + + (date(2025, 1, 16), [Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]), + + (date(2027, 2, 19), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2027, 2, 19, 7, 38)), + Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]), + + (date(2020, 1, 2), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 2, 1, 32)), + Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[5]], + datetime(2020, 1, 2, 16, 41))]), + + (date(2020, 1, 12), + [Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)), + Event(EventType.CONJUNCTION, [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)), + Event(EventType.CONJUNCTION, [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]), + + (date(2020, 2, 10), + [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'), + Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]), + + (date(2020, 3, 24), + [Event(EventType.MAXIMAL_ELONGATION, [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'), + Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 3, 24, 15, 39)), + Event(EventType.MAXIMAL_ELONGATION, [ASTERS[3]], datetime(2020, 3, 24, 21, 58), + details='46.1°')]), + + (date(2005, 6, 16), + [Event(EventType.OCCULTATION, [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]), + + (date(2020, 4, 7), [Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]), + + (date(2020, 1, 29), [Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))]) +] + + +class EventTestCase(unittest.TestCase): + def setUp(self) -> None: + self.maxDiff = None + + @parameterized.expand(EXPECTED_EVENTS) + def test_search_events(self, d: date, expected_events: [Event]): + actual_events = events.get_events(d) + self.assertEqual(len(expected_events), len(actual_events), + 'Expected %d elements, got %d for date %s.\n%s' % (len(expected_events), + len(actual_events), + d.isoformat(), + actual_events)) + + for i, expected_event in enumerate(expected_events): + actual_event = actual_events[i] + # Remove unnecessary precision (seconds and microseconds) + actual_event.start_time = datetime(actual_event.start_time.year, + actual_event.start_time.month, + actual_event.start_time.day, + actual_event.start_time.hour, + actual_event.start_time.minute) + + self.assertEqual(expected_event.__dict__, actual_event.__dict__) + + def test_get_events_raises_exception_on_out_of_date_range(self): + with self.assertRaises(OutOfRangeDateError): + events.get_events(date(1789, 5, 5)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testutils.py b/tests/testutils.py new file mode 100644 index 0000000..964a3e1 --- /dev/null +++ b/tests/testutils.py @@ -0,0 +1,48 @@ +import functools +from unittest import mock + + +def expect_assertions(assert_fun, num=1): + """Asserts that an assertion function is called as expected. + + This is very useful when the assertions are in loops. + To use it, create a nested function in the the tests function. + The nested function will receive as parameter the mocked assertion function to use in place of the original one. + Finally, run the nested function. + + Example of use: + + >>> # the function we tests: + >>> def my_sum_function(n, m): + >>> # some code here + >>> pass + + >>> # The unit tests: + >>> def test_sum(self): + >>> @expect_assertions(self.assertEqual, num=10): + >>> def make_assertions(assert_equal): + >>> for i in range (-5, 5): + >>> for j in range(-5, 5): + >>> assert_equal(i + j, my_sum_function(i, j) + >>> + >>> make_assertions() # You don't need to give any parameter, the decorator does it for you. + + :param assert_fun: the assertion function to tests + :param num: the number of times the assertion function is expected to be called + """ + assert_fun_mock = mock.Mock(side_effect=assert_fun) + + def fun_decorator(fun): + @functools.wraps(fun) + def sniff_function(): + fun(assert_fun_mock) + + count = assert_fun_mock.call_count + if count != num: + raise AssertionError('Expected %d call(s) to function %s but called %d time(s).' % (num, + assert_fun.__name__, + count)) + + return sniff_function + + return fun_decorator