11# sherlock-python/inventory/tests.py
22
3- from django .test import TestCase
3+ from django .test import TestCase , Client
44from django .contrib .auth .models import User
55from django .urls import reverse
66from django .utils import timezone
77from datetime import timedelta
88
99from .models import Section , Space , Item , Student , CheckoutLog , CheckInLog , ItemLog
1010
11+ # ==============================================================================
12+ # MODEL TESTS
13+ # ==============================================================================
14+
1115class ModelTests (TestCase ):
12- """
13- A suite of tests to verify the custom logic within our models.
14- """
15- def setUp ( self ):
16- """Set up a common state for all model tests ."""
17- self .user = User .objects .create_user (username = 'testuser' , password = 'password' )
18- self .student = Student .objects .create (name = 'Test Student' , admission_number = 'T001' , student_class = 'X' , section = 'A' )
19- self .section = Section .objects .create (name = 'Test Section' , section_code = 1 )
20- self .space = Space .objects .create (name = 'Test Space' , section = self .section , space_code = 1 )
21- self .item = Item .objects .create (
16+ """Tests for custom logic within the application's models."""
17+
18+ @ classmethod
19+ def setUpTestData ( cls ):
20+ """Set up non-modified objects used by all test methods ."""
21+ cls .user = User .objects .create_user (username = 'testuser' , password = 'password' )
22+ cls .student = Student .objects .create (name = 'Test Student' , admission_number = 'T001' , student_class = 'X' , section = 'A' )
23+ cls .section = Section .objects .create (name = 'Test Section' , section_code = 1 )
24+ cls .space = Space .objects .create (name = 'Test Space' , section = cls .section , space_code = 1 , original_section_code = 1 )
25+ cls .item = Item .objects .create (
2226 name = 'Test Item' ,
23- space = self .space ,
27+ space = cls .space ,
2428 item_code = 1 ,
2529 quantity = 20 ,
2630 buffer_quantity = 5
@@ -31,9 +35,9 @@ def test_item_quantity_properties(self):
3135 self .assertEqual (self .item .checked_out_quantity , 0 )
3236 self .assertEqual (self .item .available_quantity , 15 )
3337
34- log = CheckoutLog .objects .create (item = self .item , student = self .student , quantity = 8 , due_date = timezone .now ())
38+ CheckoutLog .objects .create (item = self .item , student = self .student , quantity = 8 , due_date = timezone .now ())
3539 self .assertEqual (self .item .checked_out_quantity , 8 )
36- self .assertEqual (self .item .available_quantity , 7 )
40+ self .assertEqual (self .item .available_quantity , 7 )
3741
3842 def test_checkout_log_is_overdue (self ):
3943 """Test the is_overdue property of the CheckoutLog model."""
@@ -50,81 +54,115 @@ def test_checkout_log_is_overdue(self):
5054 def test_checkout_log_partial_returns (self ):
5155 """Test the quantity calculation properties of the CheckoutLog model."""
5256 log = CheckoutLog .objects .create (item = self .item , student = self .student , quantity = 10 , due_date = timezone .now ())
53-
5457 self .assertEqual (log .quantity_returned_so_far , 0 )
5558 self .assertEqual (log .quantity_still_on_loan , 10 )
5659
5760 CheckInLog .objects .create (checkout_log = log , quantity_returned = 4 )
61+ log .refresh_from_db ()
5862 self .assertEqual (log .quantity_returned_so_far , 4 )
5963 self .assertEqual (log .quantity_still_on_loan , 6 )
6064
61- class ViewTests (TestCase ):
65+ # ==============================================================================
66+ # VIEW & WORKFLOW TESTS
67+ # ==============================================================================
68+
69+ class ViewAndWorkflowTests (TestCase ):
6270 """
63- A suite of tests to verify that all pages load correctly and are protected.
71+ A suite of tests to verify that pages load, are protected, and that
72+ key user workflows function correctly from end-to-end.
6473 """
6574 def setUp (self ):
66- """Set up a user and some data for view tests."""
75+ """Set up a logged-in client and base data for all view tests."""
6776 self .user = User .objects .create_user (username = 'testuser' , password = 'password123' )
6877 self .client .login (username = 'testuser' , password = 'password123' )
78+
6979 self .section = Section .objects .create (name = 'Test Section' , section_code = 1 )
70- self .space = Space .objects .create (name = 'Test Space' , section = self .section , space_code = 1 )
80+ self .space = Space .objects .create (name = 'Test Space' , section = self .section , space_code = 1 , original_section_code = 1 )
7181 self .item = Item .objects .create (name = 'Test Item' , space = self .space , item_code = 1 , quantity = 10 )
82+ self .student = Student .objects .create (name = 'Test Student' , admission_number = 'T001' , student_class = 'X' , section = 'A' )
7283
7384 def test_all_pages_load_correctly (self ):
7485 """Test that all main pages return a 200 OK status code for a logged-in user."""
7586 urls = [
7687 reverse ('inventory:dashboard' ),
77- reverse ('inventory:section_list ' ),
88+ reverse ('inventory:inventory_browser ' ),
7889 reverse ('inventory:section_detail' , args = [self .section .section_code ]),
79- reverse ('inventory:space_list' , args = [self .section .section_code ]),
8090 reverse ('inventory:space_detail' , args = [self .section .section_code , self .space .space_code ]),
81- reverse ('inventory:item_list' , args = [self .section .section_code , self .space .space_code ]),
8291 reverse ('inventory:item_detail' , args = [self .section .section_code , self .space .space_code , self .item .item_code ]),
8392 reverse ('inventory:student_list' ),
93+ reverse ('inventory:student_detail' , args = [self .student .id ]),
8494 reverse ('inventory:on_loan_dashboard' ),
8595 reverse ('inventory:overdue_report' ),
96+ reverse ('inventory:low_stock_report' ),
8697 reverse ('inventory:search' ),
98+ reverse ('inventory:print_queue' ),
8799 ]
88100 for url in urls :
89101 response = self .client .get (url )
90102 self .assertEqual (response .status_code , 200 , f"Failed to load page: { url } " )
91103
92104 def test_pages_redirect_if_not_logged_in (self ):
93- """Test that protected pages redirect to the login screen for an anonymous user."""
105+ """Test that a protected page redirects to the login screen for an anonymous user."""
94106 self .client .logout ()
95107 response = self .client .get (reverse ('inventory:dashboard' ))
96- self .assertEqual (response .status_code , 302 )
108+ self .assertEqual (response .status_code , 302 )
97109 self .assertIn (reverse ('login' ), response .url )
98110
99- class StockAdjustmentWorkflowTests (TestCase ):
100- """
101- An end-to-end test for the full stock adjustment workflow.
102- """
103- def setUp (self ):
104- self .user = User .objects .create_user (username = 'testuser' , password = 'password123' )
105- self .client .login (username = 'testuser' , password = 'password123' )
106- self .section = Section .objects .create (name = 'Test Section' , section_code = 1 )
107- self .space = Space .objects .create (name = 'Test Space' , section = self .section , space_code = 1 )
108- self .item = Item .objects .create (name = 'Test Item' , space = self .space , item_code = 1 , quantity = 10 )
109-
110111 def test_receive_stock_workflow (self ):
111- """Test the process of receiving new stock for an item."""
112+ """Test the end-to-end process of receiving new stock for an item."""
112113 self .assertEqual (Item .objects .get (id = self .item .id ).quantity , 10 )
113114 self .assertEqual (ItemLog .objects .count (), 0 )
114115
115116 url = reverse ('inventory:adjust_stock' , args = [self .section .section_code , self .space .space_code , self .item .item_code , 'RECEIVED' ])
117+ response = self .client .post (url , {'quantity' : '5' , 'notes' : 'New shipment arrived' })
116118
117- response = self .client .post (url , {
118- 'quantity' : '5' ,
119- 'notes' : 'New shipment arrived'
120- })
121-
122- self .assertEqual (response .status_code , 302 )
123119 self .assertRedirects (response , self .item .get_absolute_url ())
124-
125120 self .assertEqual (Item .objects .get (id = self .item .id ).quantity , 15 )
126121 self .assertEqual (ItemLog .objects .count (), 1 )
127122 log = ItemLog .objects .first ()
128123 self .assertEqual (log .action , 'RECEIVED' )
129124 self .assertEqual (log .quantity_change , 5 )
130- self .assertEqual (log .notes , 'New shipment arrived' )
125+
126+ def test_full_checkout_and_return_workflow (self ):
127+ """Test a complete checkout, check-in, and loan history workflow."""
128+ # 1. Start a checkout session for the student
129+ checkout_url = reverse ('inventory:checkout_session' , args = [self .student .id ])
130+ session = self .client .session
131+ session ['checkout_items' ] = {str (self .item .id ): 2 }
132+ session .save ()
133+
134+ # 2. Complete the checkout
135+ response = self .client .post (checkout_url , {
136+ 'complete_checkout' : 'true' ,
137+ 'due_date_option' : 'days' ,
138+ 'days_to_return' : '7' ,
139+ 'notes' : 'Project work' ,
140+ })
141+ self .assertRedirects (response , reverse ('inventory:student_detail' , args = [self .student .id ]))
142+
143+ # Verify the checkout log was created
144+ self .assertEqual (CheckoutLog .objects .count (), 1 )
145+ log = CheckoutLog .objects .first ()
146+ self .assertEqual (log .student , self .student )
147+ self .assertEqual (log .item , self .item )
148+ self .assertEqual (log .quantity , 2 )
149+ self .assertIsNone (log .return_date )
150+
151+ # 3. Process a partial return
152+ check_in_url = reverse ('inventory:process_check_in' , args = [log .id ])
153+ response = self .client .post (check_in_url , {'quantity_returned' : '1' })
154+ self .assertRedirects (response , reverse ('inventory:check_in_page' , args = [log .id ]))
155+
156+ # Verify the state after partial return
157+ log .refresh_from_db ()
158+ self .assertEqual (log .quantity_still_on_loan , 1 )
159+ self .assertIsNone (log .return_date ) # Should still be on loan
160+
161+ # 4. Process the final return
162+ response = self .client .post (check_in_url , {'quantity_returned' : '1' })
163+ self .assertRedirects (response , reverse ('inventory:on_loan_dashboard' ))
164+
165+ # Verify the loan is now closed
166+ log .refresh_from_db ()
167+ self .assertEqual (log .quantity_still_on_loan , 0 )
168+ self .assertIsNotNone (log .return_date )
0 commit comments