MukeshKapoor25 commited on
Commit
2df8dce
·
1 Parent(s): fde0e19

feat(system_users): Make full_name and role_id optional in SystemUserModel

Browse files

- Update SystemUserModel to make full_name and role_id optional fields with default None values
- Add error handling in SystemUserService.list_users() to gracefully handle validation errors
- Provide sensible defaults (username for full_name, "role_unknown" for role_id) when fields are missing
- Skip users that fail validation after retry to prevent service disruption
- Add comprehensive PostgreSQL connection pool fixture for integration tests
- Create test database schema with merchants_ref, catalogue_ref, and employees_ref tables
- Update connection pool acquisition test to use new test_pg_pool fixture
- Improve robustness of user listing when dealing with incomplete or legacy user records

app/system_users/models/model.py CHANGED
@@ -11,8 +11,8 @@ class SystemUserModel(BaseModel):
11
  username: str = Field(..., description="Unique username", min_length=3, max_length=50)
12
  email: EmailStr = Field(..., description="User email address")
13
  password_hash: str = Field(..., description="Bcrypt hashed password")
14
- full_name: str = Field(..., description="Full name of the user")
15
- role_id: str = Field(..., description="Role identifier e.g. role_distributor_manager")
16
  merchant_id: Optional[str] = Field(None, description="Owning merchant id")
17
  is_active: bool = Field(default=True, description="Active flag")
18
  last_login: Optional[datetime] = Field(None, description="Timestamp of last successful login")
 
11
  username: str = Field(..., description="Unique username", min_length=3, max_length=50)
12
  email: EmailStr = Field(..., description="User email address")
13
  password_hash: str = Field(..., description="Bcrypt hashed password")
14
+ full_name: Optional[str] = Field(None, description="Full name of the user")
15
+ role_id: Optional[str] = Field(None, description="Role identifier e.g. role_distributor_manager")
16
  merchant_id: Optional[str] = Field(None, description="Owning merchant id")
17
  is_active: bool = Field(default=True, description="Active flag")
18
  last_login: Optional[datetime] = Field(None, description="Timestamp of last successful login")
test_list_users_debug.py DELETED
@@ -1,91 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Debug script for list_users functionality.
4
- Tests pagination, filtering, and projection.
5
- """
6
- import asyncio
7
- from app.nosql import DatabaseConnection
8
- from app.system_users.services.service import SystemUserService
9
- from app.core.config import settings
10
-
11
-
12
- async def main():
13
- # Connect to database
14
- await DatabaseConnection.connect()
15
- db = DatabaseConnection.get_database()
16
-
17
- # Create service
18
- service = SystemUserService(db)
19
-
20
- print("=" * 80)
21
- print("DEBUGGING list_users() METHOD")
22
- print("=" * 80)
23
-
24
- # Check total users in collection
25
- total_in_db = await service.collection.count_documents({})
26
- print(f"\n✓ Total users in DB: {total_in_db}")
27
-
28
- # Check users with is_active = True
29
- active_count = await service.collection.count_documents({"is_active": True})
30
- print(f"✓ Active users in DB: {active_count}")
31
-
32
- # Check users with is_active = False
33
- inactive_count = await service.collection.count_documents({"is_active": False})
34
- print(f"✓ Inactive users in DB: {inactive_count}")
35
-
36
- print("\n" + "-" * 80)
37
- print("TEST 1: List all users (no filter, no projection)")
38
- print("-" * 80)
39
- users, total_count = await service.list_users(page=1, page_size=20)
40
- print(f"Returned {len(users)} users, total_count: {total_count}")
41
- if users:
42
- print(f"First user: {users[0]}")
43
-
44
- print("\n" + "-" * 80)
45
- print("TEST 2: List active users only")
46
- print("-" * 80)
47
- users, total_count = await service.list_users(page=1, page_size=20, is_active=True)
48
- print(f"Returned {len(users)} users, total_count: {total_count}")
49
- if users:
50
- print(f"First user: {users[0]}")
51
-
52
- print("\n" + "-" * 80)
53
- print("TEST 3: List with projection_list")
54
- print("-" * 80)
55
- users, total_count = await service.list_users(
56
- page=1,
57
- page_size=20,
58
- projection_list=["user_id", "username", "email", "role_id"]
59
- )
60
- print(f"Returned {len(users)} users, total_count: {total_count}")
61
- if users:
62
- print(f"First user: {users[0]}")
63
-
64
- print("\n" + "-" * 80)
65
- print("TEST 4: Pagination - Page 1")
66
- print("-" * 80)
67
- users, total_count = await service.list_users(page=1, page_size=5)
68
- print(f"Page 1: Returned {len(users)} users")
69
-
70
- print("\n" + "-" * 80)
71
- print("TEST 5: Pagination - Page 2")
72
- print("-" * 80)
73
- users, total_count = await service.list_users(page=2, page_size=5)
74
- print(f"Page 2: Returned {len(users)} users")
75
-
76
- print("\n" + "-" * 80)
77
- print("TEST 6: Direct MongoDB query (comparison)")
78
- print("-" * 80)
79
- cursor = service.collection.find({}).skip(0).limit(20).sort("created_at", -1)
80
- direct_users = []
81
- async for user in cursor:
82
- direct_users.append(user)
83
- print(f"Direct MongoDB query returned: {len(direct_users)} users")
84
-
85
- print("\n" + "=" * 80)
86
- print("DEBUG COMPLETE")
87
- print("=" * 80)
88
-
89
-
90
- if __name__ == "__main__":
91
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/conftest.py CHANGED
@@ -97,3 +97,85 @@ async def test_pg_conn():
97
  # Cleanup: drop test data
98
  await conn.execute("DROP TABLE IF EXISTS trans.merchants_ref")
99
  await conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  # Cleanup: drop test data
98
  await conn.execute("DROP TABLE IF EXISTS trans.merchants_ref")
99
  await conn.close()
100
+
101
+
102
+
103
+ @pytest.fixture(scope="session")
104
+ async def test_pg_pool():
105
+ """
106
+ Provide a PostgreSQL connection pool for the test session.
107
+ Initializes the pool at the start and closes it at the end.
108
+ """
109
+ from app.postgres import PostgreSQLConnectionPool
110
+
111
+ # Initialize connection pool
112
+ try:
113
+ await PostgreSQLConnectionPool.initialize()
114
+
115
+ # Create trans schema and tables
116
+ conn = await PostgreSQLConnectionPool.get_connection()
117
+ try:
118
+ await conn.execute("CREATE SCHEMA IF NOT EXISTS trans")
119
+
120
+ # Create merchants_ref table
121
+ await conn.execute("""
122
+ CREATE TABLE IF NOT EXISTS trans.merchants_ref (
123
+ merchant_id TEXT PRIMARY KEY,
124
+ merchant_code TEXT NOT NULL,
125
+ merchant_type TEXT NOT NULL,
126
+ parent_merchant_id TEXT,
127
+ status TEXT NOT NULL,
128
+ city TEXT,
129
+ state TEXT,
130
+ gst_number TEXT,
131
+ created_at TIMESTAMP NOT NULL,
132
+ updated_at TIMESTAMP NOT NULL
133
+ )
134
+ """)
135
+
136
+ # Create catalogue_ref table
137
+ await conn.execute("""
138
+ CREATE TABLE IF NOT EXISTS trans.catalogue_ref (
139
+ catalogue_id TEXT PRIMARY KEY,
140
+ catalogue_type TEXT NOT NULL,
141
+ catalogue_name TEXT NOT NULL,
142
+ sku TEXT,
143
+ barcode_number TEXT,
144
+ hsn_code TEXT,
145
+ gst_rate NUMERIC(5,2),
146
+ mrp NUMERIC(12,2),
147
+ base_price NUMERIC(12,2),
148
+ track_inventory BOOLEAN,
149
+ batch_managed BOOLEAN,
150
+ status TEXT NOT NULL,
151
+ created_at TIMESTAMP NOT NULL
152
+ )
153
+ """)
154
+
155
+ # Create employees_ref table
156
+ await conn.execute("""
157
+ CREATE TABLE IF NOT EXISTS trans.employees_ref (
158
+ employee_id TEXT PRIMARY KEY,
159
+ employee_code TEXT NOT NULL,
160
+ full_name TEXT NOT NULL,
161
+ designation TEXT NOT NULL,
162
+ department TEXT,
163
+ manager_id TEXT,
164
+ city TEXT,
165
+ state TEXT,
166
+ status TEXT NOT NULL,
167
+ system_login_enabled BOOLEAN,
168
+ created_at TIMESTAMP NOT NULL
169
+ )
170
+ """)
171
+ finally:
172
+ await PostgreSQLConnectionPool.release_connection(conn)
173
+
174
+ yield PostgreSQLConnectionPool
175
+
176
+ except Exception as e:
177
+ # If PostgreSQL is not available, skip tests that need it
178
+ pytest.skip(f"PostgreSQL not available: {e}")
179
+ finally:
180
+ # Cleanup: close connection pool
181
+ await PostgreSQLConnectionPool.close()
tests/test_properties_sync_infrastructure.py CHANGED
@@ -202,7 +202,7 @@ async def test_property_metrics_tracking(entity_type, success_operations, failur
202
  @given(
203
  num_operations=st.integers(min_value=1, max_value=20)
204
  )
205
- async def test_property_connection_pool_acquisition_and_release(num_operations):
206
  """
207
  Feature: postgres-sync, Property 10: Connection pool acquisition and release
208
 
@@ -210,9 +210,7 @@ async def test_property_connection_pool_acquisition_and_release(num_operations):
210
  before execution and release it after completion.
211
  Validates: Requirements 5.2, 5.3
212
  """
213
- # Skip if PostgreSQL is not initialized (e.g., in CI without database)
214
- if not PostgreSQLConnectionPool.is_initialized():
215
- pytest.skip("PostgreSQL connection pool not initialized")
216
 
217
  # Reset metrics before test
218
  reset_connection_pool_metrics()
 
202
  @given(
203
  num_operations=st.integers(min_value=1, max_value=20)
204
  )
205
+ async def test_property_connection_pool_acquisition_and_release(test_pg_pool, num_operations):
206
  """
207
  Feature: postgres-sync, Property 10: Connection pool acquisition and release
208
 
 
210
  before execution and release it after completion.
211
  Validates: Requirements 5.2, 5.3
212
  """
213
+ # test_pg_pool fixture ensures PostgreSQL is initialized
 
 
214
 
215
  # Reset metrics before test
216
  reset_connection_pool_metrics()