jebin2 commited on
Commit
fef313c
·
1 Parent(s): a1b468e

worker test

Browse files
Files changed (1) hide show
  1. tests/test_worker_pool.py +327 -0
tests/test_worker_pool.py CHANGED
@@ -1411,6 +1411,19 @@ class TestUserModel(IntegrationBase):
1411
  credits = Column(Integer, default=100)
1412
 
1413
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1414
  @pytest.fixture
1415
  async def integration_db():
1416
  """Create a real SQLite database for integration tests."""
@@ -1962,6 +1975,320 @@ class TestIntegrationOrphanedJobs:
1962
  assert "shutdown" in job.error_message.lower()
1963
 
1964
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1965
  if __name__ == "__main__":
1966
  pytest.main([__file__, "-v"])
1967
 
 
 
1411
  credits = Column(Integer, default=100)
1412
 
1413
 
1414
+ class TestApiKeyUsageModel(IntegrationBase):
1415
+ """Real API key usage model for integration tests."""
1416
+ __tablename__ = "test_api_key_usage"
1417
+
1418
+ id = Column(Integer, primary_key=True, autoincrement=True)
1419
+ key_index = Column(Integer, unique=True, index=True, nullable=False)
1420
+ total_requests = Column(Integer, default=0)
1421
+ success_count = Column(Integer, default=0)
1422
+ failure_count = Column(Integer, default=0)
1423
+ last_error = Column(Text, nullable=True)
1424
+ last_used_at = Column(DateTime, nullable=True)
1425
+
1426
+
1427
  @pytest.fixture
1428
  async def integration_db():
1429
  """Create a real SQLite database for integration tests."""
 
1975
  assert "shutdown" in job.error_message.lower()
1976
 
1977
 
1978
+ # =============================================================================
1979
+ # 16. API Key Manager Integration Tests (REAL, not mocked!)
1980
+ # =============================================================================
1981
+
1982
+ class TestIntegrationApiKeyManager:
1983
+ """
1984
+ Integration tests for api_key_manager.py with REAL database.
1985
+ These tests verify the double-commit fix actually works.
1986
+ """
1987
+
1988
+ @pytest.mark.asyncio
1989
+ async def test_record_usage_does_not_commit_on_its_own(self, integration_db):
1990
+ """
1991
+ Verify record_usage does NOT commit - caller must commit.
1992
+ This tests the double-commit fix.
1993
+ """
1994
+ session_maker = integration_db["session_maker"]
1995
+
1996
+ # Create initial usage record
1997
+ async with session_maker() as session:
1998
+ usage = TestApiKeyUsageModel(
1999
+ key_index=0,
2000
+ total_requests=0,
2001
+ success_count=0,
2002
+ failure_count=0
2003
+ )
2004
+ session.add(usage)
2005
+ await session.commit()
2006
+
2007
+ # Now simulate what record_usage does (without commit)
2008
+ async with session_maker() as session:
2009
+ result = await session.execute(
2010
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 0)
2011
+ )
2012
+ usage = result.scalar_one()
2013
+
2014
+ # Modify like record_usage does
2015
+ usage.total_requests += 1
2016
+ usage.success_count += 1
2017
+ usage.last_used_at = datetime.utcnow()
2018
+
2019
+ # DON'T commit - simulating the fixed behavior
2020
+ # await session.commit() # This is what we removed!
2021
+
2022
+ # Session closes without commit - changes should be LOST
2023
+
2024
+ # Verify changes were NOT persisted (rolled back)
2025
+ async with session_maker() as session:
2026
+ result = await session.execute(
2027
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 0)
2028
+ )
2029
+ usage = result.scalar_one()
2030
+
2031
+ assert usage.total_requests == 0, "Changes should NOT persist without commit!"
2032
+
2033
+ @pytest.mark.asyncio
2034
+ async def test_usage_persists_when_caller_commits(self, integration_db):
2035
+ """
2036
+ Verify changes persist when caller commits the transaction.
2037
+ """
2038
+ session_maker = integration_db["session_maker"]
2039
+
2040
+ # Create initial usage record
2041
+ async with session_maker() as session:
2042
+ usage = TestApiKeyUsageModel(
2043
+ key_index=1,
2044
+ total_requests=0,
2045
+ success_count=0,
2046
+ failure_count=0
2047
+ )
2048
+ session.add(usage)
2049
+ await session.commit()
2050
+
2051
+ # Modify and commit (like _process_job does)
2052
+ async with session_maker() as session:
2053
+ result = await session.execute(
2054
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 1)
2055
+ )
2056
+ usage = result.scalar_one()
2057
+
2058
+ usage.total_requests += 1
2059
+ usage.success_count += 1
2060
+ usage.last_used_at = datetime.utcnow()
2061
+
2062
+ # Caller commits (this is correct behavior)
2063
+ await session.commit()
2064
+
2065
+ # Verify changes WERE persisted
2066
+ async with session_maker() as session:
2067
+ result = await session.execute(
2068
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 1)
2069
+ )
2070
+ usage = result.scalar_one()
2071
+
2072
+ assert usage.total_requests == 1, "Changes should persist when caller commits"
2073
+ assert usage.success_count == 1
2074
+
2075
+ @pytest.mark.asyncio
2076
+ async def test_least_used_key_selection(self, integration_db):
2077
+ """
2078
+ Test that the least-used key is selected correctly.
2079
+ """
2080
+ session_maker = integration_db["session_maker"]
2081
+
2082
+ # Create usage records with different counts
2083
+ async with session_maker() as session:
2084
+ # Key 0: 10 requests
2085
+ usage0 = TestApiKeyUsageModel(key_index=0, total_requests=10)
2086
+ # Key 1: 5 requests (least used)
2087
+ usage1 = TestApiKeyUsageModel(key_index=1, total_requests=5)
2088
+ # Key 2: 15 requests
2089
+ usage2 = TestApiKeyUsageModel(key_index=2, total_requests=15)
2090
+
2091
+ session.add_all([usage0, usage1, usage2])
2092
+ await session.commit()
2093
+
2094
+ # Query for least used (like get_least_used_key does)
2095
+ async with session_maker() as session:
2096
+ result = await session.execute(
2097
+ select(TestApiKeyUsageModel).order_by(TestApiKeyUsageModel.total_requests)
2098
+ )
2099
+ usages = result.scalars().all()
2100
+
2101
+ assert usages[0].key_index == 1, "Key with least requests should be first"
2102
+ assert usages[0].total_requests == 5
2103
+
2104
+ @pytest.mark.asyncio
2105
+ async def test_new_key_created_in_same_transaction(self, integration_db):
2106
+ """
2107
+ Test that new key creation happens in same transaction (not committed separately).
2108
+ """
2109
+ session_maker = integration_db["session_maker"]
2110
+
2111
+ # Start transaction, add new key, but DON'T commit
2112
+ async with session_maker() as session:
2113
+ new_usage = TestApiKeyUsageModel(
2114
+ key_index=99,
2115
+ total_requests=0,
2116
+ success_count=0,
2117
+ failure_count=0
2118
+ )
2119
+ session.add(new_usage)
2120
+ # No commit - transaction rolls back
2121
+
2122
+ # Verify key was NOT created
2123
+ async with session_maker() as session:
2124
+ result = await session.execute(
2125
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 99)
2126
+ )
2127
+ usage = result.scalar_one_or_none()
2128
+
2129
+ assert usage is None, "Key should not exist without commit"
2130
+
2131
+ @pytest.mark.asyncio
2132
+ async def test_failure_recording(self, integration_db):
2133
+ """
2134
+ Test that failure count and error message are recorded correctly.
2135
+ """
2136
+ session_maker = integration_db["session_maker"]
2137
+
2138
+ # Create and modify in same transaction
2139
+ async with session_maker() as session:
2140
+ usage = TestApiKeyUsageModel(
2141
+ key_index=10,
2142
+ total_requests=0,
2143
+ success_count=0,
2144
+ failure_count=0
2145
+ )
2146
+ session.add(usage)
2147
+
2148
+ # Simulate failure
2149
+ usage.total_requests += 1
2150
+ usage.failure_count += 1
2151
+ usage.last_error = "API rate limit exceeded"[:1000]
2152
+ usage.last_used_at = datetime.utcnow()
2153
+
2154
+ await session.commit()
2155
+
2156
+ # Verify
2157
+ async with session_maker() as session:
2158
+ result = await session.execute(
2159
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 10)
2160
+ )
2161
+ usage = result.scalar_one()
2162
+
2163
+ assert usage.total_requests == 1
2164
+ assert usage.failure_count == 1
2165
+ assert usage.success_count == 0
2166
+ assert "rate limit" in usage.last_error.lower()
2167
+
2168
+ @pytest.mark.asyncio
2169
+ async def test_transaction_atomicity_job_and_usage(self, integration_db):
2170
+ """
2171
+ Test that job update and usage recording are atomic.
2172
+ If we fail before commit, NEITHER should persist.
2173
+ """
2174
+ session_maker = integration_db["session_maker"]
2175
+
2176
+ # Create job and usage
2177
+ async with session_maker() as session:
2178
+ job = TestJobModel(
2179
+ job_id="atomic-job",
2180
+ user_id="user",
2181
+ job_type="text",
2182
+ status="queued",
2183
+ priority="fast",
2184
+ created_at=datetime.utcnow()
2185
+ )
2186
+ usage = TestApiKeyUsageModel(
2187
+ key_index=20,
2188
+ total_requests=0,
2189
+ success_count=0,
2190
+ failure_count=0
2191
+ )
2192
+ session.add_all([job, usage])
2193
+ await session.commit()
2194
+
2195
+ # Simulate processing: update both, but crash before commit
2196
+ async with session_maker() as session:
2197
+ # Update job
2198
+ result = await session.execute(
2199
+ select(TestJobModel).where(TestJobModel.job_id == "atomic-job")
2200
+ )
2201
+ job = result.scalar_one()
2202
+ job.status = "completed"
2203
+ job.completed_at = datetime.utcnow()
2204
+
2205
+ # Update usage
2206
+ result = await session.execute(
2207
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 20)
2208
+ )
2209
+ usage = result.scalar_one()
2210
+ usage.total_requests += 1
2211
+ usage.success_count += 1
2212
+
2213
+ # CRASH! (no commit - simulating failure)
2214
+
2215
+ # Verify NEITHER persisted
2216
+ async with session_maker() as session:
2217
+ result = await session.execute(
2218
+ select(TestJobModel).where(TestJobModel.job_id == "atomic-job")
2219
+ )
2220
+ job = result.scalar_one()
2221
+ assert job.status == "queued", "Job should still be queued (transaction rolled back)"
2222
+
2223
+ result = await session.execute(
2224
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 20)
2225
+ )
2226
+ usage = result.scalar_one()
2227
+ assert usage.total_requests == 0, "Usage should be unchanged (transaction rolled back)"
2228
+
2229
+ @pytest.mark.asyncio
2230
+ async def test_transaction_atomicity_both_persist_on_commit(self, integration_db):
2231
+ """
2232
+ Test that job update and usage recording BOTH persist when we commit.
2233
+ """
2234
+ session_maker = integration_db["session_maker"]
2235
+
2236
+ # Create job and usage
2237
+ async with session_maker() as session:
2238
+ job = TestJobModel(
2239
+ job_id="atomic-success",
2240
+ user_id="user",
2241
+ job_type="text",
2242
+ status="queued",
2243
+ priority="fast",
2244
+ created_at=datetime.utcnow()
2245
+ )
2246
+ usage = TestApiKeyUsageModel(
2247
+ key_index=21,
2248
+ total_requests=0,
2249
+ success_count=0,
2250
+ failure_count=0
2251
+ )
2252
+ session.add_all([job, usage])
2253
+ await session.commit()
2254
+
2255
+ # Process successfully
2256
+ async with session_maker() as session:
2257
+ # Update job
2258
+ result = await session.execute(
2259
+ select(TestJobModel).where(TestJobModel.job_id == "atomic-success")
2260
+ )
2261
+ job = result.scalar_one()
2262
+ job.status = "completed"
2263
+ job.completed_at = datetime.utcnow()
2264
+
2265
+ # Update usage
2266
+ result = await session.execute(
2267
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 21)
2268
+ )
2269
+ usage = result.scalar_one()
2270
+ usage.total_requests += 1
2271
+ usage.success_count += 1
2272
+
2273
+ # COMMIT!
2274
+ await session.commit()
2275
+
2276
+ # Verify BOTH persisted
2277
+ async with session_maker() as session:
2278
+ result = await session.execute(
2279
+ select(TestJobModel).where(TestJobModel.job_id == "atomic-success")
2280
+ )
2281
+ job = result.scalar_one()
2282
+ assert job.status == "completed", "Job should be completed"
2283
+
2284
+ result = await session.execute(
2285
+ select(TestApiKeyUsageModel).where(TestApiKeyUsageModel.key_index == 21)
2286
+ )
2287
+ usage = result.scalar_one()
2288
+ assert usage.total_requests == 1, "Usage should be updated"
2289
+
2290
+
2291
  if __name__ == "__main__":
2292
  pytest.main([__file__, "-v"])
2293
 
2294
+