@@ -1144,3 +1144,265 @@ func TestDeleteOldAIBridgeRecords(t *testing.T) {
11441144 require .NoError (t , err )
11451145 require .Len (t , newToolUsages , 1 , "near threshold tool usages should not be deleted" )
11461146}
1147+
1148+ func TestDeleteOldAuditLogs (t * testing.T ) {
1149+ t .Parallel ()
1150+
1151+ t .Run ("RetentionEnabled" , func (t * testing.T ) {
1152+ t .Parallel ()
1153+
1154+ ctx := testutil .Context (t , testutil .WaitShort )
1155+
1156+ clk := quartz .NewMock (t )
1157+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1158+ retentionPeriod := 30 * 24 * time .Hour // 30 days
1159+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour ) // 31 days ago (older than threshold)
1160+ beforeThreshold := now .Add (- 15 * 24 * time .Hour ) // 15 days ago (newer than threshold)
1161+ clk .Set (now ).MustWait (ctx )
1162+
1163+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1164+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1165+ user := dbgen .User (t , db , database.User {})
1166+ org := dbgen .Organization (t , db , database.Organization {})
1167+
1168+ // Create old audit log (should be deleted)
1169+ oldLog := dbgen .AuditLog (t , db , database.AuditLog {
1170+ UserID : user .ID ,
1171+ OrganizationID : org .ID ,
1172+ Time : afterThreshold ,
1173+ Action : database .AuditActionCreate ,
1174+ ResourceType : database .ResourceTypeWorkspace ,
1175+ })
1176+
1177+ // Create recent audit log (should be kept)
1178+ recentLog := dbgen .AuditLog (t , db , database.AuditLog {
1179+ UserID : user .ID ,
1180+ OrganizationID : org .ID ,
1181+ Time : beforeThreshold ,
1182+ Action : database .AuditActionCreate ,
1183+ ResourceType : database .ResourceTypeWorkspace ,
1184+ })
1185+
1186+ // Run the purge with configured retention period
1187+ done := awaitDoTick (ctx , t , clk )
1188+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1189+ Retention : codersdk.RetentionConfig {
1190+ AuditLogs : serpent .Duration (retentionPeriod ),
1191+ },
1192+ }, clk )
1193+ defer closer .Close ()
1194+ testutil .TryReceive (ctx , t , done )
1195+
1196+ // Verify results by querying all audit logs
1197+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1198+ LimitOpt : 100 ,
1199+ })
1200+ require .NoError (t , err )
1201+
1202+ logIDs := make ([]uuid.UUID , len (logs ))
1203+ for i , log := range logs {
1204+ logIDs [i ] = log .AuditLog .ID
1205+ }
1206+
1207+ require .NotContains (t , logIDs , oldLog .ID , "old audit log should be deleted" )
1208+ require .Contains (t , logIDs , recentLog .ID , "recent audit log should be kept" )
1209+ })
1210+
1211+ t .Run ("RetentionDisabled" , func (t * testing.T ) {
1212+ t .Parallel ()
1213+
1214+ ctx := testutil .Context (t , testutil .WaitShort )
1215+
1216+ clk := quartz .NewMock (t )
1217+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1218+ oldTime := now .Add (- 365 * 24 * time .Hour ) // 1 year ago
1219+ clk .Set (now ).MustWait (ctx )
1220+
1221+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1222+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1223+ user := dbgen .User (t , db , database.User {})
1224+ org := dbgen .Organization (t , db , database.Organization {})
1225+
1226+ // Create old audit log (should NOT be deleted when retention is 0)
1227+ oldLog := dbgen .AuditLog (t , db , database.AuditLog {
1228+ UserID : user .ID ,
1229+ OrganizationID : org .ID ,
1230+ Time : oldTime ,
1231+ Action : database .AuditActionCreate ,
1232+ ResourceType : database .ResourceTypeWorkspace ,
1233+ })
1234+
1235+ // Run the purge with retention disabled (0)
1236+ done := awaitDoTick (ctx , t , clk )
1237+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1238+ Retention : codersdk.RetentionConfig {
1239+ AuditLogs : serpent .Duration (0 ), // disabled
1240+ },
1241+ }, clk )
1242+ defer closer .Close ()
1243+ testutil .TryReceive (ctx , t , done )
1244+
1245+ // Verify old log is still present
1246+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1247+ LimitOpt : 100 ,
1248+ })
1249+ require .NoError (t , err )
1250+
1251+ logIDs := make ([]uuid.UUID , len (logs ))
1252+ for i , log := range logs {
1253+ logIDs [i ] = log .AuditLog .ID
1254+ }
1255+
1256+ require .Contains (t , logIDs , oldLog .ID , "old audit log should NOT be deleted when retention is disabled" )
1257+ })
1258+
1259+ t .Run ("GlobalRetentionFallback" , func (t * testing.T ) {
1260+ t .Parallel ()
1261+
1262+ ctx := testutil .Context (t , testutil .WaitShort )
1263+
1264+ clk := quartz .NewMock (t )
1265+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1266+ retentionPeriod := 30 * 24 * time .Hour // 30 days
1267+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour ) // 31 days ago (older than threshold)
1268+ beforeThreshold := now .Add (- 15 * 24 * time .Hour ) // 15 days ago (newer than threshold)
1269+ clk .Set (now ).MustWait (ctx )
1270+
1271+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1272+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1273+ user := dbgen .User (t , db , database.User {})
1274+ org := dbgen .Organization (t , db , database.Organization {})
1275+
1276+ // Create old audit log (should be deleted)
1277+ oldLog := dbgen .AuditLog (t , db , database.AuditLog {
1278+ UserID : user .ID ,
1279+ OrganizationID : org .ID ,
1280+ Time : afterThreshold ,
1281+ Action : database .AuditActionCreate ,
1282+ ResourceType : database .ResourceTypeWorkspace ,
1283+ })
1284+
1285+ // Create recent audit log (should be kept)
1286+ recentLog := dbgen .AuditLog (t , db , database.AuditLog {
1287+ UserID : user .ID ,
1288+ OrganizationID : org .ID ,
1289+ Time : beforeThreshold ,
1290+ Action : database .AuditActionCreate ,
1291+ ResourceType : database .ResourceTypeWorkspace ,
1292+ })
1293+
1294+ // Run the purge with global retention (audit logs retention is 0, so it falls back)
1295+ done := awaitDoTick (ctx , t , clk )
1296+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1297+ Retention : codersdk.RetentionConfig {
1298+ Global : serpent .Duration (retentionPeriod ), // Use global
1299+ AuditLogs : serpent .Duration (0 ), // Not set, should fall back to global
1300+ },
1301+ }, clk )
1302+ defer closer .Close ()
1303+ testutil .TryReceive (ctx , t , done )
1304+
1305+ // Verify results
1306+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1307+ LimitOpt : 100 ,
1308+ })
1309+ require .NoError (t , err )
1310+
1311+ logIDs := make ([]uuid.UUID , len (logs ))
1312+ for i , log := range logs {
1313+ logIDs [i ] = log .AuditLog .ID
1314+ }
1315+
1316+ require .NotContains (t , logIDs , oldLog .ID , "old audit log should be deleted via global retention" )
1317+ require .Contains (t , logIDs , recentLog .ID , "recent audit log should be kept" )
1318+ })
1319+
1320+ t .Run ("ConnectionEventsNotDeleted" , func (t * testing.T ) {
1321+ t .Parallel ()
1322+
1323+ ctx := testutil .Context (t , testutil .WaitShort )
1324+
1325+ clk := quartz .NewMock (t )
1326+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1327+ retentionPeriod := 30 * 24 * time .Hour // 30 days
1328+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour ) // 31 days ago (older than threshold)
1329+ clk .Set (now ).MustWait (ctx )
1330+
1331+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1332+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1333+ user := dbgen .User (t , db , database.User {})
1334+ org := dbgen .Organization (t , db , database.Organization {})
1335+
1336+ // Create old connection events (should NOT be deleted by audit logs retention)
1337+ oldConnectLog := dbgen .AuditLog (t , db , database.AuditLog {
1338+ UserID : user .ID ,
1339+ OrganizationID : org .ID ,
1340+ Time : afterThreshold ,
1341+ Action : database .AuditActionConnect ,
1342+ ResourceType : database .ResourceTypeWorkspace ,
1343+ })
1344+
1345+ oldDisconnectLog := dbgen .AuditLog (t , db , database.AuditLog {
1346+ UserID : user .ID ,
1347+ OrganizationID : org .ID ,
1348+ Time : afterThreshold ,
1349+ Action : database .AuditActionDisconnect ,
1350+ ResourceType : database .ResourceTypeWorkspace ,
1351+ })
1352+
1353+ oldOpenLog := dbgen .AuditLog (t , db , database.AuditLog {
1354+ UserID : user .ID ,
1355+ OrganizationID : org .ID ,
1356+ Time : afterThreshold ,
1357+ Action : database .AuditActionOpen ,
1358+ ResourceType : database .ResourceTypeWorkspace ,
1359+ })
1360+
1361+ oldCloseLog := dbgen .AuditLog (t , db , database.AuditLog {
1362+ UserID : user .ID ,
1363+ OrganizationID : org .ID ,
1364+ Time : afterThreshold ,
1365+ Action : database .AuditActionClose ,
1366+ ResourceType : database .ResourceTypeWorkspace ,
1367+ })
1368+
1369+ // Create old non-connection audit log (should be deleted)
1370+ oldCreateLog := dbgen .AuditLog (t , db , database.AuditLog {
1371+ UserID : user .ID ,
1372+ OrganizationID : org .ID ,
1373+ Time : afterThreshold ,
1374+ Action : database .AuditActionCreate ,
1375+ ResourceType : database .ResourceTypeWorkspace ,
1376+ })
1377+
1378+ // Run the purge with audit logs retention enabled
1379+ done := awaitDoTick (ctx , t , clk )
1380+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1381+ Retention : codersdk.RetentionConfig {
1382+ AuditLogs : serpent .Duration (retentionPeriod ),
1383+ },
1384+ }, clk )
1385+ defer closer .Close ()
1386+ testutil .TryReceive (ctx , t , done )
1387+
1388+ // Verify results
1389+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1390+ LimitOpt : 100 ,
1391+ })
1392+ require .NoError (t , err )
1393+
1394+ logIDs := make ([]uuid.UUID , len (logs ))
1395+ for i , log := range logs {
1396+ logIDs [i ] = log .AuditLog .ID
1397+ }
1398+
1399+ // Connection events should NOT be deleted by audit logs retention
1400+ require .Contains (t , logIDs , oldConnectLog .ID , "old connect log should NOT be deleted by audit logs retention" )
1401+ require .Contains (t , logIDs , oldDisconnectLog .ID , "old disconnect log should NOT be deleted by audit logs retention" )
1402+ require .Contains (t , logIDs , oldOpenLog .ID , "old open log should NOT be deleted by audit logs retention" )
1403+ require .Contains (t , logIDs , oldCloseLog .ID , "old close log should NOT be deleted by audit logs retention" )
1404+
1405+ // Non-connection event should be deleted
1406+ require .NotContains (t , logIDs , oldCreateLog .ID , "old create log should be deleted by audit logs retention" )
1407+ })
1408+ }
0 commit comments