Merge pull request #396 from SchrodingersGat/can-has-tests

Implementing some more unit testing
This commit is contained in:
Oliver 2019-06-17 22:56:52 +10:00 committed by GitHub
commit e347f2ce80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 373 additions and 68 deletions

View File

@ -4,6 +4,8 @@ omit =
# Do not run coverage on migration files # Do not run coverage on migration files
*/migrations/* */migrations/*
InvenTree/manage.py InvenTree/manage.py
InvenTree/keygen.py
Inventree/InvenTree/middleware.py Inventree/InvenTree/middleware.py
Inventree/InvenTree/utils.py Inventree/InvenTree/utils.py
Inventree/InvenTree/wsgi.py Inventree/InvenTree/wsgi.py
InvenTree/users/apps.py

View File

@ -6,7 +6,6 @@ import io
import json import json
import os.path import os.path
from PIL import Image from PIL import Image
import requests
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
@ -30,67 +29,8 @@ def TestIfImageURL(url):
'.jpg', '.jpeg', '.jpg', '.jpeg',
'.png', '.bmp', '.png', '.bmp',
'.tif', '.tiff', '.tif', '.tiff',
'.webp', '.webp', '.gif',
] ]
def DownloadExternalFile(url, **kwargs):
""" Attempt to download an external file
Args:
url - External URL
"""
result = {
'status': False,
'url': url,
'file': None,
'status_code': 200,
}
headers = {'User-Agent': 'Mozilla/5.0'}
max_size = kwargs.get('max_size', 1048576) # 1MB default limit
# Get the HEAD for the file
try:
head = requests.head(url, stream=True, headers=headers)
except:
result['error'] = 'Error retrieving HEAD data'
return result
if not head.status_code == 200:
result['error'] = 'Incorrect HEAD status code'
result['status_code'] = head.status_code
return result
try:
filesize = int(head.headers['Content-Length'])
except ValueError:
result['error'] = 'Could not decode filesize'
result['extra'] = head.headers['Content-Length']
return result
if filesize > max_size:
result['error'] = 'File size too large ({s})'.format(s=filesize)
return result
# All checks have passed - download the file
try:
request = requests.get(url, stream=True, headers=headers)
except:
result['error'] = 'Error retriving GET data'
return result
try:
dl_file = io.StringIO(request.text)
result['status'] = True
result['file'] = dl_file
return result
except:
result['error'] = 'Could not convert downloaded data to file'
return result
def str2bool(text, test=True): def str2bool(text, test=True):

View File

@ -0,0 +1,102 @@
from django.test import TestCase
import django.core.exceptions as django_exceptions
from .validators import validate_overage, validate_part_name
from . import helpers
class ValidatorTest(TestCase):
""" Simple tests for custom field validators """
def test_part_name(self):
""" Test part name validator """
validate_part_name('hello world')
with self.assertRaises(django_exceptions.ValidationError):
validate_part_name('This | name is not } valid')
def test_overage(self):
""" Test overage validator """
validate_overage("100%")
validate_overage("10")
validate_overage("45.2 %")
with self.assertRaises(django_exceptions.ValidationError):
validate_overage("-1")
with self.assertRaises(django_exceptions.ValidationError):
validate_overage("-2.04 %")
with self.assertRaises(django_exceptions.ValidationError):
validate_overage("105%")
with self.assertRaises(django_exceptions.ValidationError):
validate_overage("xxx %")
with self.assertRaises(django_exceptions.ValidationError):
validate_overage("aaaa")
class TestHelpers(TestCase):
""" Tests for InvenTree helper functions """
def test_image_url(self):
""" Test if a filename looks like an image """
for name in ['ape.png', 'bat.GiF', 'apple.WeBP', 'BiTMap.Bmp']:
self.assertTrue(helpers.TestIfImageURL(name))
for name in ['no.doc', 'nah.pdf', 'whatpng']:
self.assertFalse(helpers.TestIfImageURL(name))
def test_str2bool(self):
""" Test string to boolean conversion """
for s in ['yes', 'Y', 'ok', '1', 'OK', 'Ok', 'tRuE', 'oN']:
self.assertTrue(helpers.str2bool(s))
self.assertFalse(helpers.str2bool(s, test=False))
for s in ['nO', '0', 'none', 'noNE', None, False, 'falSe', 'off']:
self.assertFalse(helpers.str2bool(s))
self.assertTrue(helpers.str2bool(s, test=False))
for s in ['wombat', '', 'xxxx']:
self.assertFalse(helpers.str2bool(s))
self.assertFalse(helpers.str2bool(s, test=False))
class TestQuoteWrap(TestCase):
""" Tests for string wrapping """
def test_single(self):
self.assertEqual(helpers.WrapWithQuotes('hello'), '"hello"')
self.assertEqual(helpers.WrapWithQuotes('hello"'), '"hello"')
class TestMakeBarcoede(TestCase):
""" Tests for barcode string creation """
def test_barcode(self):
data = {
'animal': 'cat',
'legs': 3,
'noise': 'purr'
}
bc = helpers.MakeBarcode("part", 3, "www.google.com", data)
self.assertIn('animal', bc)
self.assertIn('tool', bc)
self.assertIn('"tool": "InvenTree"', bc)
class TestDownloadFile(TestCase):
def test_download(self):
helpers.DownloadFile("hello world", "out.txt")
helpers.DownloadFile(bytes("hello world".encode("utf8")), "out.bin")

View File

@ -14,6 +14,7 @@ class BuildTestSimple(TestCase):
def setUp(self): def setUp(self):
part = Part.objects.create(name='Test part', part = Part.objects.create(name='Test part',
description='Simple description') description='Simple description')
Build.objects.create(part=part, Build.objects.create(part=part,
batch='B1', batch='B1',
status=BuildStatus.PENDING, status=BuildStatus.PENDING,
@ -35,6 +36,8 @@ class BuildTestSimple(TestCase):
self.assertEqual(b.batch, 'B2') self.assertEqual(b.batch, 'B2')
self.assertEqual(b.quantity, 21) self.assertEqual(b.quantity, 21)
self.assertEqual(str(b), 'Build 21 x Test part - Simple description')
def test_url(self): def test_url(self):
b1 = Build.objects.get(pk=1) b1 = Build.objects.get(pk=1)
self.assertEqual(b1.get_absolute_url(), '/build/1/') self.assertEqual(b1.get_absolute_url(), '/build/1/')
@ -46,6 +49,8 @@ class BuildTestSimple(TestCase):
self.assertEqual(b1.is_complete, False) self.assertEqual(b1.is_complete, False)
self.assertEqual(b2.is_complete, True) self.assertEqual(b2.is_complete, True)
self.assertEqual(b2.status, BuildStatus.COMPLETE)
def test_is_active(self): def test_is_active(self):
b1 = Build.objects.get(pk=1) b1 = Build.objects.get(pk=1)
b2 = Build.objects.get(pk=2) b2 = Build.objects.get(pk=2)
@ -56,3 +61,14 @@ class BuildTestSimple(TestCase):
def test_required_parts(self): def test_required_parts(self):
# TODO - Generate BOM for test part # TODO - Generate BOM for test part
pass pass
def cancel_build(self):
""" Test build cancellation function """
build = Build.objects.get(id=1)
self.assertEqual(build.status, BuildStatus.PENDING)
build.cancelBuild()
self.assertEqual(build.status, BuildStatus.CANCELLED)

View File

@ -1,14 +1,17 @@
# Sample company data # Sample company data
- model: company.company - model: company.company
pk: 1
fields: fields:
name: ACME name: ACME
description: A Cool Military Enterprise description: A Cool Military Enterprise
- model: company.company - model: company.company
pk: 2
fields: fields:
name: Appel Computers name: Appel Computers
description: Think more differenter description: Think more differenter
- model: company.company - model: company.company
pk: 3
fields: fields:
name: Zerg Corp name: Zerg Corp
description: We eat the competition description: We eat the competition

View File

@ -15,6 +15,22 @@
supplier: 1 supplier: 1
SKU: 'ACME0002' SKU: 'ACME0002'
# Widget purchaseable from ACME
- model: company.supplierpart
pk: 100
fields:
part: 25
supplier: 1
SKU: 'ACME-WIDGET'
# Widget purchaseable from Zerg
- model: company.supplierpart
pk: 101
fields:
part: 25
supplier: 2
SKU: 'ZERG-WIDGET'
# M2x4 LPHS from Zerg Corp # M2x4 LPHS from Zerg Corp
- model: company.supplierpart - model: company.supplierpart
pk: 3 pk: 3

View File

@ -56,10 +56,10 @@ class CompanySimpleTest(TestCase):
zerg = Company.objects.get(pk=3) zerg = Company.objects.get(pk=3)
self.assertTrue(acme.has_parts) self.assertTrue(acme.has_parts)
self.assertEqual(acme.part_count, 2) self.assertEqual(acme.part_count, 3)
self.assertTrue(appel.has_parts) self.assertTrue(appel.has_parts)
self.assertEqual(appel.part_count, 1) self.assertEqual(appel.part_count, 2)
self.assertTrue(zerg.has_parts) self.assertTrue(zerg.has_parts)
self.assertEqual(zerg.part_count, 1) self.assertEqual(zerg.part_count, 1)

View File

@ -0,0 +1,3 @@
"""
The Order module is responsible for managing Orders
"""

View File

@ -0,0 +1,44 @@
# PurchaseOrder and PurchaseOrderLineItem objects for testing
# Ordering some screws from ACME
- model: order.purchaseorder
pk: 1
fields:
reference: 0001
description: "Ordering some screws"
supplier: 1
# Ordering some screws from Zerg Corp
- model: order.purchaseorder
pk: 2
fields:
reference: 0002
description: "Ordering some more screws"
supplier: 3
# Add some line items against PO 0001
# 100 x ACME0001 (M2x4 LPHS)
- model: order.purchaseorderlineitem
pk: 1
fields:
order: 1
part: 1
quantity: 100
# 250 x ACME0002 (M2x4 LPHS)
# Partially received (50)
- model: order.purchaseorderlineitem
fields:
order: 1
part: 2
quantity: 250
received: 50
# 100 x ZERGLPHS (M2x4 LPHS)
- model: order.purchaseorderlineitem
fields:
order: 2
part: 3
quantity: 100

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.2 on 2019-06-17 09:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0011_auto_20190615_1928'),
]
operations = [
migrations.AlterField(
model_name='purchaseorder',
name='creation_date',
field=models.DateField(blank=True, null=True),
),
]

View File

@ -50,6 +50,12 @@ class Order(models.Model):
return " ".join(el) return " ".join(el)
def save(self, *args, **kwargs):
if not self.creation_date:
self.creation_date = datetime.now().date()
super().save(*args, **kwargs)
class Meta: class Meta:
abstract = True abstract = True
@ -59,7 +65,7 @@ class Order(models.Model):
URL = models.URLField(blank=True, help_text=_('Link to external page')) URL = models.URLField(blank=True, help_text=_('Link to external page'))
creation_date = models.DateField(auto_now=True, editable=False) creation_date = models.DateField(blank=True, null=True)
status = models.PositiveIntegerField(default=OrderStatus.PENDING, choices=OrderStatus.items(), status = models.PositiveIntegerField(default=OrderStatus.PENDING, choices=OrderStatus.items(),
help_text='Order status') help_text='Order status')
@ -227,6 +233,16 @@ class PurchaseOrder(Order):
""" Receive a line item (or partial line item) against this PO """ Receive a line item (or partial line item) against this PO
""" """
if not self.status == OrderStatus.PLACED:
raise ValidationError({"status": _("Lines can only be received against an order marked as 'Placed'")})
try:
quantity = int(quantity)
if quantity <= 0:
raise ValidationError({"quantity": _("Quantity must be greater than zero")})
except ValueError:
raise ValidationError({"quantity": _("Invalid quantity provided")})
# Create a new stock item # Create a new stock item
if line.part: if line.part:
stock = StockItem( stock = StockItem(
@ -239,7 +255,7 @@ class PurchaseOrder(Order):
# Add a new transaction note to the newly created stock item # Add a new transaction note to the newly created stock item
stock.addTransactionNote("Received items", user, "Received {q} items against order '{po}'".format( stock.addTransactionNote("Received items", user, "Received {q} items against order '{po}'".format(
q=line.receive_quantity, q=quantity,
po=str(self)) po=str(self))
) )

View File

@ -1 +1,146 @@
# TODO - Implement tests for the order app from django.test import TestCase
import django.core.exceptions as django_exceptions
from part.models import Part
from .models import PurchaseOrder, PurchaseOrderLineItem
from stock.models import StockLocation
from company.models import SupplierPart
from InvenTree.status_codes import OrderStatus
class OrderTest(TestCase):
"""
Tests to ensure that the order models are functioning correctly.
"""
fixtures = [
'company',
'supplier_part',
'category',
'part',
'location',
'stock',
'order'
]
def test_basics(self):
""" Basic tests e.g. repr functions etc """
order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.get_absolute_url(), '/order/purchase-order/1/')
self.assertEqual(str(order), 'PO 1')
line = PurchaseOrderLineItem.objects.get(pk=1)
self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO 1)")
def test_on_order(self):
""" There should be 3 separate items on order for the M2x4 LPHS part """
part = Part.objects.get(name='M2x4 LPHS')
open_orders = []
for supplier in part.supplier_parts.all():
open_orders += supplier.open_orders()
self.assertEqual(len(open_orders), 3)
# Test the total on-order quantity
self.assertEqual(part.on_order, 400)
def test_add_items(self):
""" Test functions for adding line items to an order """
order = PurchaseOrder.objects.get(pk=1)
self.assertEqual(order.status, OrderStatus.PENDING)
self.assertEqual(order.lines.count(), 2)
sku = SupplierPart.objects.get(SKU='ACME-WIDGET')
part = sku.part
# Try to order some invalid things
with self.assertRaises(django_exceptions.ValidationError):
order.add_line_item(sku, -999)
with self.assertRaises(django_exceptions.ValidationError):
order.add_line_item(sku, 'not a number')
# Order the part
self.assertEqual(part.on_order, 0)
order.add_line_item(sku, 100)
self.assertEqual(part.on_order, 100)
self.assertEqual(order.lines.count(), 3)
# Order the same part again (it should be merged)
order.add_line_item(sku, 50)
self.assertEqual(order.lines.count(), 3)
self.assertEqual(part.on_order, 150)
# Try to order a supplier part from the wrong supplier
sku = SupplierPart.objects.get(SKU='ZERG-WIDGET')
with self.assertRaises(django_exceptions.ValidationError):
order.add_line_item(sku, 99)
def test_receive(self):
""" Test order receiving functions """
part = Part.objects.get(name='M2x4 LPHS')
# Receive some items
line = PurchaseOrderLineItem.objects.get(id=1)
order = line.order
loc = StockLocation.objects.get(id=1)
# There should be two lines against this order
self.assertEqual(len(order.pending_line_items()), 2)
# Should fail, as order is 'PENDING' not 'PLACED"
self.assertEqual(order.status, OrderStatus.PENDING)
with self.assertRaises(django_exceptions.ValidationError):
order.receive_line_item(line, loc, 50, user=None)
order.place_order()
self.assertEqual(order.status, OrderStatus.PLACED)
order.receive_line_item(line, loc, 50, user=None)
self.assertEqual(line.remaining(), 50)
self.assertEqual(part.on_order, 350)
# Try to order some invalid things
with self.assertRaises(django_exceptions.ValidationError):
order.receive_line_item(line, loc, -10, user=None)
with self.assertRaises(django_exceptions.ValidationError):
order.receive_line_item(line, loc, 'not a number', user=None)
# Receive the rest of the items
order.receive_line_item(line, loc, 50, user=None)
line = PurchaseOrderLineItem.objects.get(id=2)
order.receive_line_item(line, loc, 2 * line.quantity, user=None)
self.assertEqual(part.on_order, 100)
self.assertEqual(order.status, OrderStatus.COMPLETE)
def test_export(self):
""" Test order exporting """
order = PurchaseOrder.objects.get(pk=1)
output = order.export_to_file(format='csv')
self.assertIn('M2x4 LPHS', output)
self.assertIn('Line,Part,Description', output)

View File

@ -32,7 +32,7 @@ test:
coverage: coverage:
python3 InvenTree/manage.py check python3 InvenTree/manage.py check
coverage run InvenTree/manage.py test build company part stock order coverage run InvenTree/manage.py test build company part stock order InvenTree
coverage html coverage html
documentation: documentation: