Some checks failed
Build containers when image tags change / build-if-image-changed (., web, containers, main container, git.baumann.gr/adebaumann/labhelper) (push) Successful in 2m26s
Build containers when image tags change / build-if-image-changed (data-loader, loader, initContainers, init-container, git.baumann.gr/adebaumann/labhelper-data-loader) (push) Successful in 23s
SonarQube Scan / SonarQube Trigger (push) Failing after 19s
490 lines
17 KiB
Python
490 lines
17 KiB
Python
from django.contrib.admin.sites import AdminSite
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.db import IntegrityError
|
|
from django.test import Client, TestCase
|
|
from django.urls import reverse
|
|
|
|
from .admin import BoxAdmin, BoxTypeAdmin, ThingAdmin, ThingTypeAdmin
|
|
from .models import Box, BoxType, Thing, ThingType
|
|
|
|
|
|
class BoxTypeModelTests(TestCase):
|
|
"""Tests for the BoxType model."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.box_type = BoxType.objects.create(
|
|
name='Small Box',
|
|
width=100,
|
|
height=50,
|
|
length=150
|
|
)
|
|
|
|
def test_box_type_str_returns_name(self):
|
|
"""BoxType __str__ should return the name."""
|
|
self.assertEqual(str(self.box_type), 'Small Box')
|
|
|
|
def test_box_type_creation(self):
|
|
"""BoxType should be created with correct attributes."""
|
|
self.assertEqual(self.box_type.name, 'Small Box')
|
|
self.assertEqual(self.box_type.width, 100)
|
|
self.assertEqual(self.box_type.height, 50)
|
|
self.assertEqual(self.box_type.length, 150)
|
|
|
|
def test_box_type_ordering(self):
|
|
"""BoxTypes should be ordered by name."""
|
|
BoxType.objects.create(name='Alpha Box', width=10, height=10, length=10)
|
|
BoxType.objects.create(name='Zeta Box', width=20, height=20, length=20)
|
|
box_types = list(BoxType.objects.values_list('name', flat=True))
|
|
self.assertEqual(box_types, ['Alpha Box', 'Small Box', 'Zeta Box'])
|
|
|
|
|
|
class BoxModelTests(TestCase):
|
|
"""Tests for the Box model."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.box_type = BoxType.objects.create(
|
|
name='Standard Box',
|
|
width=200,
|
|
height=100,
|
|
length=300
|
|
)
|
|
self.box = Box.objects.create(
|
|
id='BOX001',
|
|
box_type=self.box_type
|
|
)
|
|
|
|
def test_box_str_returns_id(self):
|
|
"""Box __str__ should return the box ID."""
|
|
self.assertEqual(str(self.box), 'BOX001')
|
|
|
|
def test_box_creation(self):
|
|
"""Box should be created with correct attributes."""
|
|
self.assertEqual(self.box.id, 'BOX001')
|
|
self.assertEqual(self.box.box_type, self.box_type)
|
|
|
|
def test_box_type_relationship(self):
|
|
"""Box should be accessible from BoxType via related_name."""
|
|
self.assertIn(self.box, self.box_type.boxes.all())
|
|
|
|
def test_box_id_max_length(self):
|
|
"""Box ID should accept up to 10 characters."""
|
|
box = Box.objects.create(id='ABCD123456', box_type=self.box_type)
|
|
self.assertEqual(len(box.id), 10)
|
|
|
|
def test_box_type_protect_on_delete(self):
|
|
"""Deleting a BoxType with boxes should raise IntegrityError."""
|
|
with self.assertRaises(IntegrityError):
|
|
self.box_type.delete()
|
|
|
|
def test_box_type_delete_when_no_boxes(self):
|
|
"""Deleting a BoxType without boxes should succeed."""
|
|
empty_type = BoxType.objects.create(
|
|
name='Empty Type',
|
|
width=10,
|
|
height=10,
|
|
length=10
|
|
)
|
|
empty_type_id = empty_type.id
|
|
empty_type.delete()
|
|
self.assertFalse(BoxType.objects.filter(id=empty_type_id).exists())
|
|
|
|
|
|
class BoxTypeAdminTests(TestCase):
|
|
"""Tests for the BoxType admin configuration."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.site = AdminSite()
|
|
self.admin = BoxTypeAdmin(BoxType, self.site)
|
|
|
|
def test_list_display(self):
|
|
"""BoxTypeAdmin should display correct fields."""
|
|
self.assertEqual(
|
|
self.admin.list_display,
|
|
('name', 'width', 'height', 'length')
|
|
)
|
|
|
|
def test_search_fields(self):
|
|
"""BoxTypeAdmin should search by name."""
|
|
self.assertEqual(self.admin.search_fields, ('name',))
|
|
|
|
|
|
class BoxAdminTests(TestCase):
|
|
"""Tests for the Box admin configuration."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.site = AdminSite()
|
|
self.admin = BoxAdmin(Box, self.site)
|
|
|
|
def test_list_display(self):
|
|
"""BoxAdmin should display correct fields."""
|
|
self.assertEqual(self.admin.list_display, ('id', 'box_type'))
|
|
|
|
def test_list_filter(self):
|
|
"""BoxAdmin should filter by box_type."""
|
|
self.assertEqual(self.admin.list_filter, ('box_type',))
|
|
|
|
def test_search_fields(self):
|
|
"""BoxAdmin should search by id."""
|
|
self.assertEqual(self.admin.search_fields, ('id',))
|
|
|
|
|
|
class ThingTypeModelTests(TestCase):
|
|
"""Tests for the ThingType model."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.thing_type = ThingType.objects.create(name='Electronics')
|
|
|
|
def test_thing_type_str_returns_name(self):
|
|
"""ThingType __str__ should return the name."""
|
|
self.assertEqual(str(self.thing_type), 'Electronics')
|
|
|
|
def test_thing_type_creation(self):
|
|
"""ThingType should be created with correct attributes."""
|
|
self.assertEqual(self.thing_type.name, 'Electronics')
|
|
|
|
def test_thing_type_hierarchy(self):
|
|
"""ThingType should support parent-child relationships."""
|
|
child = ThingType.objects.create(
|
|
name='Resistors',
|
|
parent=self.thing_type
|
|
)
|
|
self.assertEqual(child.parent, self.thing_type)
|
|
self.assertIn(child, self.thing_type.children.all())
|
|
|
|
def test_thing_type_is_leaf_node(self):
|
|
"""ThingType without children should be a leaf node."""
|
|
self.assertTrue(self.thing_type.is_leaf_node())
|
|
|
|
def test_thing_type_is_not_leaf_with_children(self):
|
|
"""ThingType with children should not be a leaf node."""
|
|
ThingType.objects.create(name='Capacitors', parent=self.thing_type)
|
|
self.assertFalse(self.thing_type.is_leaf_node())
|
|
|
|
def test_thing_type_ancestors(self):
|
|
"""ThingType should return correct ancestors."""
|
|
child = ThingType.objects.create(
|
|
name='Resistors',
|
|
parent=self.thing_type
|
|
)
|
|
grandchild = ThingType.objects.create(
|
|
name='10k Resistors',
|
|
parent=child
|
|
)
|
|
ancestors = list(grandchild.get_ancestors())
|
|
self.assertEqual(ancestors, [self.thing_type, child])
|
|
|
|
def test_thing_type_descendants(self):
|
|
"""ThingType should return correct descendants."""
|
|
child = ThingType.objects.create(
|
|
name='Resistors',
|
|
parent=self.thing_type
|
|
)
|
|
grandchild = ThingType.objects.create(
|
|
name='10k Resistors',
|
|
parent=child
|
|
)
|
|
descendants = list(self.thing_type.get_descendants())
|
|
self.assertEqual(descendants, [child, grandchild])
|
|
|
|
def test_thing_type_level(self):
|
|
"""ThingType should have correct level in hierarchy."""
|
|
self.assertEqual(self.thing_type.level, 0)
|
|
child = ThingType.objects.create(
|
|
name='Resistors',
|
|
parent=self.thing_type
|
|
)
|
|
self.assertEqual(child.level, 1)
|
|
|
|
|
|
class ThingModelTests(TestCase):
|
|
"""Tests for the Thing model."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.box_type = BoxType.objects.create(
|
|
name='Standard Box',
|
|
width=200,
|
|
height=100,
|
|
length=300
|
|
)
|
|
self.box = Box.objects.create(
|
|
id='BOX001',
|
|
box_type=self.box_type
|
|
)
|
|
self.thing_type = ThingType.objects.create(name='Electronics')
|
|
self.thing = Thing.objects.create(
|
|
name='Arduino Uno',
|
|
thing_type=self.thing_type,
|
|
box=self.box
|
|
)
|
|
|
|
def test_thing_str_returns_name(self):
|
|
"""Thing __str__ should return the name."""
|
|
self.assertEqual(str(self.thing), 'Arduino Uno')
|
|
|
|
def test_thing_creation(self):
|
|
"""Thing should be created with correct attributes."""
|
|
self.assertEqual(self.thing.name, 'Arduino Uno')
|
|
self.assertEqual(self.thing.thing_type, self.thing_type)
|
|
self.assertEqual(self.thing.box, self.box)
|
|
|
|
def test_thing_optional_description(self):
|
|
"""Thing description should be optional."""
|
|
self.assertEqual(self.thing.description, '')
|
|
self.thing.description = 'A microcontroller board'
|
|
self.thing.save()
|
|
self.thing.refresh_from_db()
|
|
self.assertEqual(self.thing.description, 'A microcontroller board')
|
|
|
|
def test_thing_optional_picture(self):
|
|
"""Thing picture should be optional."""
|
|
self.assertEqual(self.thing.picture.name, '')
|
|
|
|
def test_thing_with_picture(self):
|
|
"""Thing should accept an image upload."""
|
|
# Create a simple 1x1 pixel PNG
|
|
image_data = (
|
|
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00'
|
|
b'\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00'
|
|
b'\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82'
|
|
)
|
|
image = SimpleUploadedFile(
|
|
name='test.png',
|
|
content=image_data,
|
|
content_type='image/png'
|
|
)
|
|
thing = Thing.objects.create(
|
|
name='Test Item',
|
|
thing_type=self.thing_type,
|
|
box=self.box,
|
|
picture=image
|
|
)
|
|
self.assertTrue(thing.picture.name.startswith('things/'))
|
|
# Clean up
|
|
thing.picture.delete()
|
|
|
|
def test_thing_ordering(self):
|
|
"""Things should be ordered by name."""
|
|
Thing.objects.create(
|
|
name='Zeta Item',
|
|
thing_type=self.thing_type,
|
|
box=self.box
|
|
)
|
|
Thing.objects.create(
|
|
name='Alpha Item',
|
|
thing_type=self.thing_type,
|
|
box=self.box
|
|
)
|
|
things = list(Thing.objects.values_list('name', flat=True))
|
|
self.assertEqual(things, ['Alpha Item', 'Arduino Uno', 'Zeta Item'])
|
|
|
|
def test_thing_type_relationship(self):
|
|
"""Thing should be accessible from ThingType via related_name."""
|
|
self.assertIn(self.thing, self.thing_type.things.all())
|
|
|
|
def test_thing_box_relationship(self):
|
|
"""Thing should be accessible from Box via related_name."""
|
|
self.assertIn(self.thing, self.box.things.all())
|
|
|
|
def test_thing_type_protect_on_delete(self):
|
|
"""Deleting a ThingType with things should raise IntegrityError."""
|
|
with self.assertRaises(IntegrityError):
|
|
self.thing_type.delete()
|
|
|
|
def test_box_protect_on_delete_with_things(self):
|
|
"""Deleting a Box with things should raise IntegrityError."""
|
|
with self.assertRaises(IntegrityError):
|
|
self.box.delete()
|
|
|
|
|
|
class ThingTypeAdminTests(TestCase):
|
|
"""Tests for the ThingType admin configuration."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.site = AdminSite()
|
|
self.admin = ThingTypeAdmin(ThingType, self.site)
|
|
|
|
def test_search_fields(self):
|
|
"""ThingTypeAdmin should search by name."""
|
|
self.assertEqual(self.admin.search_fields, ('name',))
|
|
|
|
|
|
class ThingAdminTests(TestCase):
|
|
"""Tests for the Thing admin configuration."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.site = AdminSite()
|
|
self.admin = ThingAdmin(Thing, self.site)
|
|
|
|
def test_list_display(self):
|
|
"""ThingAdmin should display correct fields."""
|
|
self.assertEqual(
|
|
self.admin.list_display,
|
|
('name', 'thing_type', 'box')
|
|
)
|
|
|
|
def test_list_filter(self):
|
|
"""ThingAdmin should filter by thing_type and box."""
|
|
self.assertEqual(self.admin.list_filter, ('thing_type', 'box'))
|
|
|
|
def test_search_fields(self):
|
|
"""ThingAdmin should search by name and description."""
|
|
self.assertEqual(self.admin.search_fields, ('name', 'description'))
|
|
|
|
|
|
class IndexViewTests(TestCase):
|
|
"""Tests for the index view."""
|
|
|
|
def setUp(self):
|
|
"""Set up test client."""
|
|
self.client = Client()
|
|
|
|
def test_index_returns_200(self):
|
|
"""Index page should return 200 status."""
|
|
response = self.client.get('/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_index_contains_labhelper(self):
|
|
"""Index page should contain LabHelper title."""
|
|
response = self.client.get('/')
|
|
self.assertContains(response, 'LabHelper')
|
|
|
|
def test_index_contains_admin_link(self):
|
|
"""Index page should contain link to admin."""
|
|
response = self.client.get('/')
|
|
self.assertContains(response, '/admin/')
|
|
|
|
|
|
class BoxDetailViewTests(TestCase):
|
|
"""Tests for the box detail view."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
self.client = Client()
|
|
self.box_type = BoxType.objects.create(
|
|
name='Standard Box',
|
|
width=200,
|
|
height=100,
|
|
length=300
|
|
)
|
|
self.box = Box.objects.create(
|
|
id='BOX001',
|
|
box_type=self.box_type
|
|
)
|
|
self.thing_type = ThingType.objects.create(name='Electronics')
|
|
|
|
def test_box_detail_returns_200(self):
|
|
"""Box detail page should return 200 status."""
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_box_detail_returns_404_for_invalid_box(self):
|
|
"""Box detail page should return 404 for non-existent box."""
|
|
response = self.client.get('/box/INVALID/')
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def test_box_detail_shows_box_id(self):
|
|
"""Box detail page should show box ID."""
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'BOX001')
|
|
|
|
def test_box_detail_shows_box_type(self):
|
|
"""Box detail page should show box type name."""
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'Standard Box')
|
|
|
|
def test_box_detail_shows_dimensions(self):
|
|
"""Box detail page should show box dimensions."""
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, '200')
|
|
self.assertContains(response, '100')
|
|
self.assertContains(response, '300')
|
|
|
|
def test_box_detail_shows_empty_message(self):
|
|
"""Box detail page should show empty message when no things."""
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'This box is empty')
|
|
|
|
def test_box_detail_shows_thing(self):
|
|
"""Box detail page should show things in the box."""
|
|
Thing.objects.create(
|
|
name='Arduino Uno',
|
|
thing_type=self.thing_type,
|
|
box=self.box,
|
|
description='A microcontroller board'
|
|
)
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'Arduino Uno')
|
|
self.assertContains(response, 'Electronics')
|
|
self.assertContains(response, 'A microcontroller board')
|
|
|
|
def test_box_detail_shows_no_image_placeholder(self):
|
|
"""Box detail page should show placeholder for things without images."""
|
|
Thing.objects.create(
|
|
name='Test Item',
|
|
thing_type=self.thing_type,
|
|
box=self.box
|
|
)
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'No image')
|
|
|
|
def test_box_detail_shows_multiple_things(self):
|
|
"""Box detail page should show multiple things."""
|
|
Thing.objects.create(
|
|
name='Item One',
|
|
thing_type=self.thing_type,
|
|
box=self.box
|
|
)
|
|
Thing.objects.create(
|
|
name='Item Two',
|
|
thing_type=self.thing_type,
|
|
box=self.box
|
|
)
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'Item One')
|
|
self.assertContains(response, 'Item Two')
|
|
|
|
def test_box_detail_uses_correct_template(self):
|
|
"""Box detail page should use the correct template."""
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertTemplateUsed(response, 'boxes/box_detail.html')
|
|
|
|
def test_box_detail_with_image(self):
|
|
"""Box detail page should show thumbnail for things with images."""
|
|
# Create a simple 1x1 pixel PNG
|
|
image_data = (
|
|
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00'
|
|
b'\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00'
|
|
b'\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82'
|
|
)
|
|
image = SimpleUploadedFile(
|
|
name='test.png',
|
|
content=image_data,
|
|
content_type='image/png'
|
|
)
|
|
thing = Thing.objects.create(
|
|
name='Item With Image',
|
|
thing_type=self.thing_type,
|
|
box=self.box,
|
|
picture=image
|
|
)
|
|
response = self.client.get(f'/box/{self.box.id}/')
|
|
self.assertContains(response, 'Item With Image')
|
|
self.assertContains(response, '<img')
|
|
# Clean up
|
|
thing.picture.delete()
|
|
|
|
def test_box_detail_url_name(self):
|
|
"""Box detail URL should be reversible by name."""
|
|
url = reverse('box_detail', kwargs={'box_id': 'BOX001'})
|
|
self.assertEqual(url, '/box/BOX001/')
|