Skip to content

Commit

Permalink
More tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Tatsh committed Sep 14, 2023
1 parent 61e78b0 commit 89e007b
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = minchoc/wsgi.py
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Install dependencies (Yarn)
run: yarn
- name: Run tests
run: yarn test --cov=minchoc --cov-branch
run: yarn test --cov=minchoc --cov-branch --cov-fail-under=95
- if: matrix.python-version == 3.11
name: Coveralls
run: poetry run coveralls --service=github
Expand Down
2 changes: 0 additions & 2 deletions minchoc/settings.py

This file was deleted.

5 changes: 1 addition & 4 deletions minchoc/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.conf import settings
from django.urls import path

from . import views
Expand All @@ -14,7 +13,5 @@
path('api/v2/$metadata', views.metadata),
path('api/v2/package/<name>/<version>', views.fetch_package_file),
path('api/v2/package/', views.APIV2PackageView.as_view()),
path('', views.home)
]

if settings.WANT_NUGET_HOME:
urlpatterns.append(path('', views.home))
28 changes: 17 additions & 11 deletions minchoc/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
import logging
import re
import zipfile

Expand All @@ -16,7 +17,6 @@
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from loguru import logger

from .constants import FEED_XML_POST, FEED_XML_PRE
from .filteryacc import parser as filter_parser
Expand Down Expand Up @@ -47,6 +47,8 @@
}
PACKAGE_FIELDS = {f.name: f for f in Package._meta.get_fields()}

logger = logging.getLogger(__name__)


@require_http_methods(['GET'])
def home(_request: HttpRequest) -> HttpResponse:
Expand Down Expand Up @@ -122,7 +124,7 @@ def make_entry(host: str, package: Package, ending: str = '\n') -> str:
@require_http_methods(['GET'])
def find_packages_by_id(request: HttpRequest) -> HttpResponse:
if (sem_ver_level := request.GET.get('semVerLevel')):
logger.warning(f'Ignoring semVerLevel={sem_ver_level}')
logger.warning('Ignoring semVerLevel=%s', sem_ver_level)
proto = 'https' if request.is_secure() else 'http'
proto_host = f'{proto}://{request.get_host()}'
try:
Expand All @@ -143,12 +145,15 @@ def packages(request: HttpRequest) -> HttpResponse:
filter_ = request.GET.get('$filter')
order_by = request.GET.get('$orderby') or 'id'
if (sem_ver_level := request.GET.get('semVerLevel')):
logger.warning(f'Ignoring semVerLevel={sem_ver_level}')
logger.warning('Ignoring semVerLevel=%s', sem_ver_level)
if (skip := request.GET.get('$skip')):
logger.warning(f'Ignoring $skip={skip}')
logger.warning('Ignoring $skip=%s', skip)
if (top := request.GET.get('$top')):
logger.warning(f'Ignoring $top={top}')
filters = filter_parser.parse(filter_) if filter_ else {}
logger.warning('Ignoring $top=%s', top)
try:
filters = filter_parser.parse(filter_) if filter_ else {}
except SyntaxError:
return JsonResponse({'error': 'Invalid syntax in filter'}, status=400)
proto = 'https' if request.is_secure() else 'http'
proto_host = f'{proto}://{request.get_host()}'
content = '\n'.join(
Expand Down Expand Up @@ -197,6 +202,8 @@ def fetch_package_file(request: HttpRequest, name: str, version: str) -> HttpRes
package.file.delete()
package.delete()
return HttpResponse(status=204)
else:
return HttpResponse(status=405)
return HttpResponseNotFound()


Expand All @@ -211,7 +218,6 @@ def put(self, request: HttpRequest) -> HttpResponse:
if not request.content_type or not request.content_type.startswith('multipart/'):
return JsonResponse(
{'error': f'Invalid content type: {request.content_type or "unknown"}'}, status=400)
# These 2 lines must exist. Combining into 1 does not work
try:
_, files = request.parse_file_upload(request.META, request)
except MultiPartParserError:
Expand Down Expand Up @@ -243,8 +249,8 @@ def put(self, request: HttpRequest) -> HttpResponse:
for key, column_name in NUSPEC_FIELD_MAPPINGS.items():
value = root[0].find(key)
assert value is not None
if not value.text:
logger.warning(f'No value for key {key}')
if not value.text: # pragma no cover
logger.warning('No value for key %s', key)
continue
column_type = (None if column_name not in PACKAGE_FIELDS else
PACKAGE_FIELDS[column_name].get_internal_type())
Expand All @@ -262,8 +268,8 @@ def put(self, request: HttpRequest) -> HttpResponse:
new_author, _ = Author.objects.get_or_create(name=name)
new_author.save()
add_authors.append(new_author)
else:
logger.warning(f'Did not set {column_name}')
else: # pragma no cover
logger.warning('Did not set %s', column_name)
elif column_type == 'BooleanField':
setattr(new_package, column_name, value.text.lower() == 'true')
else:
Expand Down
1 change: 1 addition & 0 deletions minchoc/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pragma no cover
from django.core.wsgi import get_wsgi_application

__all__ = ('application',)
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def pytest_configure(config: pytest.Config) -> None:
'minchoc'
],
LANGUAGE_CODE='en-us',
LOGGING={},
MIDDLEWARE=[
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
Expand Down
33 changes: 33 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

from minchoc.models import Company, NugetUser, Package, Tag


@pytest.mark.django_db
def test_company_str():
company = Company.objects.create(name='Company name')
assert str(company) == 'Company name'


@pytest.mark.django_db
def test_tag_str():
tag = Tag.objects.create(name='Tag name')
assert str(tag) == 'Tag name'


@pytest.mark.django_db
def test_package_str(nuget_user: NugetUser) -> None:
package = Package.objects.create(nuget_id='somename',
title='somename',
uploader=nuget_user,
version='123.0',
version0=123,
version1=1,
size=1)
assert str(package) == 'somename 123.0'


@pytest.mark.django_db
def test_nuget_user_str(nuget_user: NugetUser) -> None:
nuget_user.base.username = 'fakename'
assert str(nuget_user) == 'fakename'
23 changes: 23 additions & 0 deletions tests/test_static.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.test import Client
import pytest


def test_home(client: Client) -> None:
response = client.get('/')
assert response.content == b'{}'
assert response.get('content-type') == 'application/json'
assert response.status_code == 200


@pytest.mark.django_db
def test_metadata(client: Client) -> None:
response = client.get('/$metadata')
assert response.get('content-type') == 'application/xml'
assert response.status_code == 200


@pytest.mark.django_db
def test_metadata_alt(client: Client) -> None:
response = client.get('/api/v2/$metadata')
assert response.get('content-type') == 'application/xml'
assert response.status_code == 200
150 changes: 139 additions & 11 deletions tests/test_workflow.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
import re
from tempfile import NamedTemporaryFile
from django.conf import settings
from django.test import Client
import pytest
import zipfile

from minchoc.models import NugetUser
from minchoc.models import NugetUser, Package

GALLERY_RE = r'/package/somename/1.0.2</d:Gallery'.encode()

def test_home(client: Client) -> None:
response = client.get('/')
assert response.content == b'{}'
assert response.get('content-type') == 'application/json'
assert response.status_code == 200

@pytest.mark.django_db
def test_packages_invalid_syntax(client: Client) -> None:
response = client.get('/Packages()',
QUERY_STRING='$filter=(tolower(Id) eq somename) and IsLatestVersion')
assert response.status_code != 200


@pytest.mark.django_db
def test_metadata(client: Client) -> None:
response = client.get('/$metadata')
assert response.get('content-type') == 'application/xml'
assert response.status_code == 200
def test_packages_with_args_not_found(client: Client) -> None:
response = client.get("/Packages(Id='fake',Version='123.0.0')",
QUERY_STRING='$filter=(tolower(Id) eq somename) and IsLatestVersion')
assert response.status_code == 404


@pytest.mark.django_db
def test_fetch_package_file(client: Client) -> None:
response = client.get('/api/v2/package/fake/123.0.0')
assert response.status_code == 404


@pytest.mark.django_db
Expand All @@ -27,7 +39,7 @@ def test_put_not_authorized(client: Client) -> None:

@pytest.mark.django_db
def test_post_not_authorized(client: Client) -> None:
response = client.post('/api/v2/package/')
response = client.put('/api/v2/package/')
assert response.json()['error'] == 'Not authorized'
assert response.status_code == 403

Expand Down Expand Up @@ -114,3 +126,119 @@ def test_put_not_zip_file(client: Client, nuget_user: NugetUser) -> None:
}) # type: ignore[arg-type]
assert response.json()['error'] == 'Not a zip file'
assert response.status_code == 400


def test_find_package_invalid_req(client: Client) -> None:
response = client.get('/FindPackagesById()')
assert response.status_code == 400


@pytest.mark.django_db
def test_put_too_many_nuspecs_post(client: Client, nuget_user: NugetUser) -> None:
with NamedTemporaryFile('rb', prefix='minchoc_test', suffix='.zip') as tf:
temp_name = tf.name
with zipfile.ZipFile(temp_name, 'w') as z:
z.writestr('a.nuspec', '')
z.writestr('b.nuspec', 'aaa')
with open(temp_name, 'rb') as f:
content = f.read()
content = b'''--1234abc\r
content-disposition: form-data; name="upload"; filename="a.zip"\r
content-type: application/zip\r
\r
''' + content + b'''\r
--1234abc--'''
response = client.post('/api/v2/package/',
content,
'multipart/form-data; boundary=1234abc',
headers={
'content-length': f'{len(content)}',
'x-nuget-apikey': nuget_user.token.hex
}) # type: ignore[arg-type])
assert (response.json()['error'] ==
'There should be exactly 1 nuspec file present. 0 or more than 1 were found.')
assert response.status_code == 400


@pytest.mark.django_db
def test_put(client: Client, nuget_user: NugetUser) -> None:
with NamedTemporaryFile('rb', prefix='minchoc_test', suffix='.nuget') as tf:
temp_name = tf.name
with zipfile.ZipFile(temp_name, 'w') as z:
z.writestr(
'a.nuspec', '''<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>somename</id>
<version>1.0.2</version>
<title>PACKAGE_NAME (Install)</title>
<authors>AUTHORS</authors>
<owners>NUGET_MAKER</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<projectUrl>https://a-url</projectUrl>
<description>DESCRIPTION</description>
<summary>SUMMARY</summary>
<tags>tag1 tag2</tags>
<packageSourceUrl>https://a-url-can-be-same-as-project</packageSourceUrl>
</metadata>
</package>''')
with open(temp_name, 'rb') as f:
content = f.read()
content = b'''--1234abc\r
content-disposition: form-data; name="upload"; filename="a.zip"\r
content-type: application/zip\r
\r
''' + content + b'''\r
--1234abc--'''
response = client.put('/api/v2/package/',
content,
'multipart/form-data; boundary=1234abc',
headers={
'content-length': f'{len(content)}',
'x-nuget-apikey': nuget_user.token.hex
}) # type: ignore[arg-type])
assert response.status_code == 201
# response = client.post('/api/v2/package/',
# content,
# 'multipart/form-data; boundary=1234abc',
# headers={
# 'content-length': f'{len(content)}',
# 'x-nuget-apikey': nuget_user.token.hex
# }) # type: ignore[arg-type])
# assert response.json()['error'] == 'Integrity error (has this already been uploaded?)'
# assert response.status_code == 400
# find_packages_by_id
response = client.get('/FindPackagesById()?semVerLevel=2.0.0&id=somename')
assert re.search(GALLERY_RE, response.content) is not None
assert response.status_code == 200
# packages
response = client.get(
'/Packages()',
QUERY_STRING="$filter=(tolower(Id) eq 'somename') and IsLatestVersion&$orderby=id"
'&semVerLevel=2.0.0&$skip=0&$top=1')
assert re.search(GALLERY_RE, response.content) is not None
assert response.status_code == 200
# packages_with_args
response = client.get("/Packages(Id='somename',Version='1.0.2')")
assert re.search(GALLERY_RE, response.content) is not None
assert response.status_code == 200
# fetch_package_file
package = Package.objects.filter(nuget_id='somename').first()
assert package is not None
assert package.download_count == 0
response = client.get('/api/v2/package/somename/1.0.2')
assert response.get('content-type') == 'application/zip'
assert response.status_code == 200
package = Package.objects.filter(nuget_id='somename').first()
assert package is not None
assert package.download_count == 1
response = client.delete('/api/v2/package/somename/1.0.2')
assert response.status_code == 405
settings.ALLOW_PACKAGE_DELETION = True
# fetch_package_file DELETE
response = client.delete('/api/v2/package/somename/1.0.2')
assert response.status_code == 403
response = client.delete('/api/v2/package/somename/1.0.2',
headers={'x-nuget-apikey':
nuget_user.token.hex}) # type: ignore[arg-type]
assert response.status_code == 204

0 comments on commit 89e007b

Please sign in to comment.