@@ -1050,3 +1050,265 @@ func TestDeleteOldAIBridgeRecords(t *testing.T) {
10501050 require .NoError (t , err )
10511051 require .Len (t , newToolUsages , 1 , "near threshold tool usages should not be deleted" )
10521052}
1053+
1054+ func TestDeleteOldAuditLogs (t * testing.T ) {
1055+ t .Parallel ()
1056+
1057+ t .Run ("RetentionEnabled" , func (t * testing.T ) {
1058+ t .Parallel ()
1059+
1060+ ctx := testutil .Context (t , testutil .WaitShort )
1061+
1062+ clk := quartz .NewMock (t )
1063+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1064+ retentionPeriod := 30 * 24 * time .Hour // 30 days
1065+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour ) // 31 days ago (older than threshold)
1066+ beforeThreshold := now .Add (- 15 * 24 * time .Hour ) // 15 days ago (newer than threshold)
1067+ clk .Set (now ).MustWait (ctx )
1068+
1069+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1070+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1071+ user := dbgen .User (t , db , database.User {})
1072+ org := dbgen .Organization (t , db , database.Organization {})
1073+
1074+ // Create old audit log (should be deleted)
1075+ oldLog := dbgen .AuditLog (t , db , database.AuditLog {
1076+ UserID : user .ID ,
1077+ OrganizationID : org .ID ,
1078+ Time : afterThreshold ,
1079+ Action : database .AuditActionCreate ,
1080+ ResourceType : database .ResourceTypeWorkspace ,
1081+ })
1082+
1083+ // Create recent audit log (should be kept)
1084+ recentLog := dbgen .AuditLog (t , db , database.AuditLog {
1085+ UserID : user .ID ,
1086+ OrganizationID : org .ID ,
1087+ Time : beforeThreshold ,
1088+ Action : database .AuditActionCreate ,
1089+ ResourceType : database .ResourceTypeWorkspace ,
1090+ })
1091+
1092+ // Run the purge with configured retention period
1093+ done := awaitDoTick (ctx , t , clk )
1094+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1095+ Retention : codersdk.RetentionConfig {
1096+ AuditLogs : serpent .Duration (retentionPeriod ),
1097+ },
1098+ }, clk )
1099+ defer closer .Close ()
1100+ testutil .TryReceive (ctx , t , done )
1101+
1102+ // Verify results by querying all audit logs
1103+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1104+ LimitOpt : 100 ,
1105+ })
1106+ require .NoError (t , err )
1107+
1108+ logIDs := make ([]uuid.UUID , len (logs ))
1109+ for i , log := range logs {
1110+ logIDs [i ] = log .AuditLog .ID
1111+ }
1112+
1113+ require .NotContains (t , logIDs , oldLog .ID , "old audit log should be deleted" )
1114+ require .Contains (t , logIDs , recentLog .ID , "recent audit log should be kept" )
1115+ })
1116+
1117+ t .Run ("RetentionDisabled" , func (t * testing.T ) {
1118+ t .Parallel ()
1119+
1120+ ctx := testutil .Context (t , testutil .WaitShort )
1121+
1122+ clk := quartz .NewMock (t )
1123+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1124+ oldTime := now .Add (- 365 * 24 * time .Hour ) // 1 year ago
1125+ clk .Set (now ).MustWait (ctx )
1126+
1127+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1128+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1129+ user := dbgen .User (t , db , database.User {})
1130+ org := dbgen .Organization (t , db , database.Organization {})
1131+
1132+ // Create old audit log (should NOT be deleted when retention is 0)
1133+ oldLog := dbgen .AuditLog (t , db , database.AuditLog {
1134+ UserID : user .ID ,
1135+ OrganizationID : org .ID ,
1136+ Time : oldTime ,
1137+ Action : database .AuditActionCreate ,
1138+ ResourceType : database .ResourceTypeWorkspace ,
1139+ })
1140+
1141+ // Run the purge with retention disabled (0)
1142+ done := awaitDoTick (ctx , t , clk )
1143+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1144+ Retention : codersdk.RetentionConfig {
1145+ AuditLogs : serpent .Duration (0 ), // disabled
1146+ },
1147+ }, clk )
1148+ defer closer .Close ()
1149+ testutil .TryReceive (ctx , t , done )
1150+
1151+ // Verify old log is still present
1152+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1153+ LimitOpt : 100 ,
1154+ })
1155+ require .NoError (t , err )
1156+
1157+ logIDs := make ([]uuid.UUID , len (logs ))
1158+ for i , log := range logs {
1159+ logIDs [i ] = log .AuditLog .ID
1160+ }
1161+
1162+ require .Contains (t , logIDs , oldLog .ID , "old audit log should NOT be deleted when retention is disabled" )
1163+ })
1164+
1165+ t .Run ("GlobalRetentionFallback" , func (t * testing.T ) {
1166+ t .Parallel ()
1167+
1168+ ctx := testutil .Context (t , testutil .WaitShort )
1169+
1170+ clk := quartz .NewMock (t )
1171+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1172+ retentionPeriod := 30 * 24 * time .Hour // 30 days
1173+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour ) // 31 days ago (older than threshold)
1174+ beforeThreshold := now .Add (- 15 * 24 * time .Hour ) // 15 days ago (newer than threshold)
1175+ clk .Set (now ).MustWait (ctx )
1176+
1177+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1178+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1179+ user := dbgen .User (t , db , database.User {})
1180+ org := dbgen .Organization (t , db , database.Organization {})
1181+
1182+ // Create old audit log (should be deleted)
1183+ oldLog := dbgen .AuditLog (t , db , database.AuditLog {
1184+ UserID : user .ID ,
1185+ OrganizationID : org .ID ,
1186+ Time : afterThreshold ,
1187+ Action : database .AuditActionCreate ,
1188+ ResourceType : database .ResourceTypeWorkspace ,
1189+ })
1190+
1191+ // Create recent audit log (should be kept)
1192+ recentLog := dbgen .AuditLog (t , db , database.AuditLog {
1193+ UserID : user .ID ,
1194+ OrganizationID : org .ID ,
1195+ Time : beforeThreshold ,
1196+ Action : database .AuditActionCreate ,
1197+ ResourceType : database .ResourceTypeWorkspace ,
1198+ })
1199+
1200+ // Run the purge with global retention (audit logs retention is 0, so it falls back)
1201+ done := awaitDoTick (ctx , t , clk )
1202+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1203+ Retention : codersdk.RetentionConfig {
1204+ Global : serpent .Duration (retentionPeriod ), // Use global
1205+ AuditLogs : serpent .Duration (0 ), // Not set, should fall back to global
1206+ },
1207+ }, clk )
1208+ defer closer .Close ()
1209+ testutil .TryReceive (ctx , t , done )
1210+
1211+ // Verify results
1212+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1213+ LimitOpt : 100 ,
1214+ })
1215+ require .NoError (t , err )
1216+
1217+ logIDs := make ([]uuid.UUID , len (logs ))
1218+ for i , log := range logs {
1219+ logIDs [i ] = log .AuditLog .ID
1220+ }
1221+
1222+ require .NotContains (t , logIDs , oldLog .ID , "old audit log should be deleted via global retention" )
1223+ require .Contains (t , logIDs , recentLog .ID , "recent audit log should be kept" )
1224+ })
1225+
1226+ t .Run ("ConnectionEventsNotDeleted" , func (t * testing.T ) {
1227+ t .Parallel ()
1228+
1229+ ctx := testutil .Context (t , testutil .WaitShort )
1230+
1231+ clk := quartz .NewMock (t )
1232+ now := time .Date (2025 , 1 , 15 , 7 , 30 , 0 , 0 , time .UTC )
1233+ retentionPeriod := 30 * 24 * time .Hour // 30 days
1234+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour ) // 31 days ago (older than threshold)
1235+ clk .Set (now ).MustWait (ctx )
1236+
1237+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
1238+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
1239+ user := dbgen .User (t , db , database.User {})
1240+ org := dbgen .Organization (t , db , database.Organization {})
1241+
1242+ // Create old connection events (should NOT be deleted by audit logs retention)
1243+ oldConnectLog := dbgen .AuditLog (t , db , database.AuditLog {
1244+ UserID : user .ID ,
1245+ OrganizationID : org .ID ,
1246+ Time : afterThreshold ,
1247+ Action : database .AuditActionConnect ,
1248+ ResourceType : database .ResourceTypeWorkspace ,
1249+ })
1250+
1251+ oldDisconnectLog := dbgen .AuditLog (t , db , database.AuditLog {
1252+ UserID : user .ID ,
1253+ OrganizationID : org .ID ,
1254+ Time : afterThreshold ,
1255+ Action : database .AuditActionDisconnect ,
1256+ ResourceType : database .ResourceTypeWorkspace ,
1257+ })
1258+
1259+ oldOpenLog := dbgen .AuditLog (t , db , database.AuditLog {
1260+ UserID : user .ID ,
1261+ OrganizationID : org .ID ,
1262+ Time : afterThreshold ,
1263+ Action : database .AuditActionOpen ,
1264+ ResourceType : database .ResourceTypeWorkspace ,
1265+ })
1266+
1267+ oldCloseLog := dbgen .AuditLog (t , db , database.AuditLog {
1268+ UserID : user .ID ,
1269+ OrganizationID : org .ID ,
1270+ Time : afterThreshold ,
1271+ Action : database .AuditActionClose ,
1272+ ResourceType : database .ResourceTypeWorkspace ,
1273+ })
1274+
1275+ // Create old non-connection audit log (should be deleted)
1276+ oldCreateLog := dbgen .AuditLog (t , db , database.AuditLog {
1277+ UserID : user .ID ,
1278+ OrganizationID : org .ID ,
1279+ Time : afterThreshold ,
1280+ Action : database .AuditActionCreate ,
1281+ ResourceType : database .ResourceTypeWorkspace ,
1282+ })
1283+
1284+ // Run the purge with audit logs retention enabled
1285+ done := awaitDoTick (ctx , t , clk )
1286+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
1287+ Retention : codersdk.RetentionConfig {
1288+ AuditLogs : serpent .Duration (retentionPeriod ),
1289+ },
1290+ }, clk )
1291+ defer closer .Close ()
1292+ testutil .TryReceive (ctx , t , done )
1293+
1294+ // Verify results
1295+ logs , err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1296+ LimitOpt : 100 ,
1297+ })
1298+ require .NoError (t , err )
1299+
1300+ logIDs := make ([]uuid.UUID , len (logs ))
1301+ for i , log := range logs {
1302+ logIDs [i ] = log .AuditLog .ID
1303+ }
1304+
1305+ // Connection events should NOT be deleted by audit logs retention
1306+ require .Contains (t , logIDs , oldConnectLog .ID , "old connect log should NOT be deleted by audit logs retention" )
1307+ require .Contains (t , logIDs , oldDisconnectLog .ID , "old disconnect log should NOT be deleted by audit logs retention" )
1308+ require .Contains (t , logIDs , oldOpenLog .ID , "old open log should NOT be deleted by audit logs retention" )
1309+ require .Contains (t , logIDs , oldCloseLog .ID , "old close log should NOT be deleted by audit logs retention" )
1310+
1311+ // Non-connection event should be deleted
1312+ require .NotContains (t , logIDs , oldCreateLog .ID , "old create log should be deleted by audit logs retention" )
1313+ })
1314+ }
0 commit comments