@@ -1065,192 +1065,135 @@ func TestDeleteOldAIBridgeRecords(t *testing.T) {
10651065func TestDeleteOldAuditLogs (t * testing.T ) {
10661066t .Parallel ()
10671067
1068- t .Run ("RetentionEnabled" ,func (t * testing.T ) {
1069- t .Parallel ()
1070-
1071- ctx := testutil .Context (t ,testutil .WaitShort )
1072-
1073- clk := quartz .NewMock (t )
1074- now := time .Date (2025 ,1 ,15 ,7 ,30 ,0 ,0 ,time .UTC )
1075- retentionPeriod := 30 * 24 * time .Hour // 30 days
1076- afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour )// 31 days ago (older than threshold)
1077- beforeThreshold := now .Add (- 15 * 24 * time .Hour )// 15 days ago (newer than threshold)
1078- clk .Set (now ).MustWait (ctx )
1079-
1080- db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
1081- logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
1082- user := dbgen .User (t ,db , database.User {})
1083- org := dbgen .Organization (t ,db , database.Organization {})
1084-
1085- // Create old audit log (should be deleted)
1086- oldLog := dbgen .AuditLog (t ,db , database.AuditLog {
1087- UserID :user .ID ,
1088- OrganizationID :org .ID ,
1089- Time :afterThreshold ,
1090- Action :database .AuditActionCreate ,
1091- ResourceType :database .ResourceTypeWorkspace ,
1092- })
1093-
1094- // Create recent audit log (should be kept)
1095- recentLog := dbgen .AuditLog (t ,db , database.AuditLog {
1096- UserID :user .ID ,
1097- OrganizationID :org .ID ,
1098- Time :beforeThreshold ,
1099- Action :database .AuditActionCreate ,
1100- ResourceType :database .ResourceTypeWorkspace ,
1101- })
1068+ now := time .Date (2025 ,1 ,15 ,7 ,30 ,0 ,0 ,time .UTC )
1069+ retentionPeriod := 30 * 24 * time .Hour
1070+ afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour )// 31 days ago (older than threshold)
1071+ beforeThreshold := now .Add (- 15 * 24 * time .Hour )// 15 days ago (newer than threshold)
11021072
1103- // Run the purge with configured retention period
1104- done := awaitDoTick (ctx ,t ,clk )
1105- closer := dbpurge .New (ctx ,logger ,db ,& codersdk.DeploymentValues {
1106- Retention : codersdk.RetentionConfig {
1073+ testCases := []struct {
1074+ name string
1075+ retentionConfig codersdk.RetentionConfig
1076+ oldLogTime time.Time
1077+ recentLogTime * time.Time // nil means no recent log created
1078+ expectOldDeleted bool
1079+ expectedLogsRemaining int
1080+ }{
1081+ {
1082+ name :"RetentionEnabled" ,
1083+ retentionConfig : codersdk.RetentionConfig {
11071084AuditLogs :serpent .Duration (retentionPeriod ),
11081085},
1109- },clk )
1110- defer closer .Close ()
1111- testutil .TryReceive (ctx ,t ,done )
1112-
1113- // Verify results by querying all audit logs
1114- logs ,err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1115- LimitOpt :100 ,
1116- })
1117- require .NoError (t ,err )
1118-
1119- logIDs := make ([]uuid.UUID ,len (logs ))
1120- for i ,log := range logs {
1121- logIDs [i ]= log .AuditLog .ID
1122- }
1123-
1124- require .NotContains (t ,logIDs ,oldLog .ID ,"old audit log should be deleted" )
1125- require .Contains (t ,logIDs ,recentLog .ID ,"recent audit log should be kept" )
1126- })
1127-
1128- t .Run ("RetentionDisabled" ,func (t * testing.T ) {
1129- t .Parallel ()
1130-
1131- ctx := testutil .Context (t ,testutil .WaitShort )
1132-
1133- clk := quartz .NewMock (t )
1134- now := time .Date (2025 ,1 ,15 ,7 ,30 ,0 ,0 ,time .UTC )
1135- oldTime := now .Add (- 365 * 24 * time .Hour )// 1 year ago
1136- clk .Set (now ).MustWait (ctx )
1137-
1138- db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
1139- logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
1140- user := dbgen .User (t ,db , database.User {})
1141- org := dbgen .Organization (t ,db , database.Organization {})
1142-
1143- // Create old audit log (should NOT be deleted when retention is 0)
1144- oldLog := dbgen .AuditLog (t ,db , database.AuditLog {
1145- UserID :user .ID ,
1146- OrganizationID :org .ID ,
1147- Time :oldTime ,
1148- Action :database .AuditActionCreate ,
1149- ResourceType :database .ResourceTypeWorkspace ,
1150- })
1151-
1152- // Run the purge with retention disabled (0)
1153- done := awaitDoTick (ctx ,t ,clk )
1154- closer := dbpurge .New (ctx ,logger ,db ,& codersdk.DeploymentValues {
1155- Retention : codersdk.RetentionConfig {
1156- AuditLogs :serpent .Duration (0 ),// disabled
1086+ oldLogTime :afterThreshold ,
1087+ recentLogTime :& beforeThreshold ,
1088+ expectOldDeleted :true ,
1089+ expectedLogsRemaining :1 ,// only recent log remains
1090+ },
1091+ {
1092+ name :"RetentionDisabled" ,
1093+ retentionConfig : codersdk.RetentionConfig {
1094+ AuditLogs :serpent .Duration (0 ),
11571095},
1158- },clk )
1159- defer closer .Close ()
1160- testutil .TryReceive (ctx ,t ,done )
1096+ oldLogTime :now .Add (- 365 * 24 * time .Hour ),// 1 year ago
1097+ recentLogTime :nil ,
1098+ expectOldDeleted :false ,
1099+ expectedLogsRemaining :1 ,// old log is kept
1100+ },
1101+ {
1102+ name :"GlobalRetentionFallback" ,
1103+ retentionConfig : codersdk.RetentionConfig {
1104+ Global :serpent .Duration (retentionPeriod ),
1105+ AuditLogs :serpent .Duration (0 ),// Not set, should fall back to global
1106+ },
1107+ oldLogTime :afterThreshold ,
1108+ recentLogTime :& beforeThreshold ,
1109+ expectOldDeleted :true ,
1110+ expectedLogsRemaining :1 ,// only recent log remains
1111+ },
1112+ }
11611113
1162- // Verify old log is still present
1163- logs ,err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1164- LimitOpt :100 ,
1165- })
1166- require .NoError (t ,err )
1114+ for _ ,tc := range testCases {
1115+ t .Run (tc .name ,func (t * testing.T ) {
1116+ t .Parallel ()
11671117
1168- logIDs := make ([]uuid.UUID ,len (logs ))
1169- for i ,log := range logs {
1170- logIDs [i ]= log .AuditLog .ID
1171- }
1118+ ctx := testutil .Context (t ,testutil .WaitShort )
1119+ clk := quartz .NewMock (t )
1120+ clk .Set (now ).MustWait (ctx )
11721121
1173- require . Contains ( t , logIDs , oldLog . ID , "old audit log should NOT be deleted when retention is disabled" )
1174- })
1122+ db , _ := dbtestutil . NewDB ( t , dbtestutil . WithDumpOnFailure () )
1123+ logger := slogtest . Make ( t , & slogtest. Options { IgnoreErrors : true })
11751124
1176- t .Run ("GlobalRetentionFallback" ,func (t * testing.T ) {
1177- t .Parallel ()
1125+ // Setup test fixtures.
1126+ user := dbgen .User (t ,db , database.User {})
1127+ org := dbgen .Organization (t ,db , database.Organization {})
11781128
1179- ctx := testutil .Context (t ,testutil .WaitShort )
1129+ // Create old audit log.
1130+ oldLog := dbgen .AuditLog (t ,db , database.AuditLog {
1131+ UserID :user .ID ,
1132+ OrganizationID :org .ID ,
1133+ Time :tc .oldLogTime ,
1134+ Action :database .AuditActionCreate ,
1135+ ResourceType :database .ResourceTypeWorkspace ,
1136+ })
11801137
1181- clk := quartz .NewMock (t )
1182- now := time .Date (2025 ,1 ,15 ,7 ,30 ,0 ,0 ,time .UTC )
1183- retentionPeriod := 30 * 24 * time .Hour // 30 days
1184- afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour )// 31 days ago (older than threshold)
1185- beforeThreshold := now .Add (- 15 * 24 * time .Hour )// 15 days ago (newer than threshold)
1186- clk .Set (now ).MustWait (ctx )
1138+ // Create recent audit log if specified.
1139+ var recentLog database.AuditLog
1140+ if tc .recentLogTime != nil {
1141+ recentLog = dbgen .AuditLog (t ,db , database.AuditLog {
1142+ UserID :user .ID ,
1143+ OrganizationID :org .ID ,
1144+ Time :* tc .recentLogTime ,
1145+ Action :database .AuditActionCreate ,
1146+ ResourceType :database .ResourceTypeWorkspace ,
1147+ })
1148+ }
11871149
1188- db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
1189- logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
1190- user := dbgen .User (t ,db , database.User {})
1191- org := dbgen .Organization (t ,db , database.Organization {})
1150+ // Run the purge.
1151+ done := awaitDoTick (ctx ,t ,clk )
1152+ closer := dbpurge .New (ctx ,logger ,db ,& codersdk.DeploymentValues {
1153+ Retention :tc .retentionConfig ,
1154+ },clk )
1155+ defer closer .Close ()
1156+ testutil .TryReceive (ctx ,t ,done )
11921157
1193- // Create old audit log (should be deleted)
1194- oldLog := dbgen .AuditLog (t ,db , database.AuditLog {
1195- UserID :user .ID ,
1196- OrganizationID :org .ID ,
1197- Time :afterThreshold ,
1198- Action :database .AuditActionCreate ,
1199- ResourceType :database .ResourceTypeWorkspace ,
1200- })
1158+ // Verify results.
1159+ logs ,err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
1160+ LimitOpt :100 ,
1161+ })
1162+ require .NoError (t ,err )
1163+ require .Len (t ,logs ,tc .expectedLogsRemaining ,"unexpected number of logs remaining" )
12011164
1202- // Create recent audit log (should be kept)
1203- recentLog := dbgen .AuditLog (t ,db , database.AuditLog {
1204- UserID :user .ID ,
1205- OrganizationID :org .ID ,
1206- Time :beforeThreshold ,
1207- Action :database .AuditActionCreate ,
1208- ResourceType :database .ResourceTypeWorkspace ,
1209- })
1165+ logIDs := make ([]uuid.UUID ,len (logs ))
1166+ for i ,log := range logs {
1167+ logIDs [i ]= log .AuditLog .ID
1168+ }
12101169
1211- // Run the purge with global retention (audit logs retention is 0, so it falls back)
1212- done := awaitDoTick (ctx ,t ,clk )
1213- closer := dbpurge .New (ctx ,logger ,db ,& codersdk.DeploymentValues {
1214- Retention : codersdk.RetentionConfig {
1215- Global :serpent .Duration (retentionPeriod ),// Use global
1216- AuditLogs :serpent .Duration (0 ),// Not set, should fall back to global
1217- },
1218- },clk )
1219- defer closer .Close ()
1220- testutil .TryReceive (ctx ,t ,done )
1170+ if tc .expectOldDeleted {
1171+ require .NotContains (t ,logIDs ,oldLog .ID ,"old audit log should be deleted" )
1172+ }else {
1173+ require .Contains (t ,logIDs ,oldLog .ID ,"old audit log should NOT be deleted" )
1174+ }
12211175
1222- // Verify results
1223- logs , err := db . GetAuditLogsOffset ( ctx , database. GetAuditLogsOffsetParams {
1224- LimitOpt : 100 ,
1176+ if tc . recentLogTime != nil {
1177+ require . Contains ( t , logIDs , recentLog . ID , "recent audit log should be kept" )
1178+ }
12251179})
1226- require .NoError (t ,err )
1227-
1228- logIDs := make ([]uuid.UUID ,len (logs ))
1229- for i ,log := range logs {
1230- logIDs [i ]= log .AuditLog .ID
1231- }
1232-
1233- require .NotContains (t ,logIDs ,oldLog .ID ,"old audit log should be deleted via global retention" )
1234- require .Contains (t ,logIDs ,recentLog .ID ,"recent audit log should be kept" )
1235- })
1180+ }
12361181
1182+ // ConnectionEventsNotDeleted is a special case that tests multiple audit
1183+ // action types, so it's kept as a separate subtest.
12371184t .Run ("ConnectionEventsNotDeleted" ,func (t * testing.T ) {
12381185t .Parallel ()
12391186
12401187ctx := testutil .Context (t ,testutil .WaitShort )
1241-
12421188clk := quartz .NewMock (t )
1243- now := time .Date (2025 ,1 ,15 ,7 ,30 ,0 ,0 ,time .UTC )
1244- retentionPeriod := 30 * 24 * time .Hour // 30 days
1245- afterThreshold := now .Add (- retentionPeriod ).Add (- 24 * time .Hour )// 31 days ago (older than threshold)
12461189clk .Set (now ).MustWait (ctx )
12471190
12481191db ,_ := dbtestutil .NewDB (t ,dbtestutil .WithDumpOnFailure ())
12491192logger := slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true })
12501193user := dbgen .User (t ,db , database.User {})
12511194org := dbgen .Organization (t ,db , database.Organization {})
12521195
1253- // Create old connection events (should NOT be deleted by audit logs retention)
1196+ // Create old connection events (should NOT be deleted by audit logs retention).
12541197oldConnectLog := dbgen .AuditLog (t ,db , database.AuditLog {
12551198UserID :user .ID ,
12561199OrganizationID :org .ID ,
@@ -1283,7 +1226,7 @@ func TestDeleteOldAuditLogs(t *testing.T) {
12831226ResourceType :database .ResourceTypeWorkspace ,
12841227})
12851228
1286- // Create old non-connection audit log (should be deleted)
1229+ // Create old non-connection audit log (should be deleted).
12871230oldCreateLog := dbgen .AuditLog (t ,db , database.AuditLog {
12881231UserID :user .ID ,
12891232OrganizationID :org .ID ,
@@ -1292,7 +1235,7 @@ func TestDeleteOldAuditLogs(t *testing.T) {
12921235ResourceType :database .ResourceTypeWorkspace ,
12931236})
12941237
1295- // Run the purge with audit logs retention enabled
1238+ // Run the purge with audit logs retention enabled.
12961239done := awaitDoTick (ctx ,t ,clk )
12971240closer := dbpurge .New (ctx ,logger ,db ,& codersdk.DeploymentValues {
12981241Retention : codersdk.RetentionConfig {
@@ -1302,24 +1245,25 @@ func TestDeleteOldAuditLogs(t *testing.T) {
13021245defer closer .Close ()
13031246testutil .TryReceive (ctx ,t ,done )
13041247
1305- // Verify results
1248+ // Verify results.
13061249logs ,err := db .GetAuditLogsOffset (ctx , database.GetAuditLogsOffsetParams {
13071250LimitOpt :100 ,
13081251})
13091252require .NoError (t ,err )
1253+ require .Len (t ,logs ,4 ,"should have 4 connection event logs remaining" )
13101254
13111255logIDs := make ([]uuid.UUID ,len (logs ))
13121256for i ,log := range logs {
13131257logIDs [i ]= log .AuditLog .ID
13141258}
13151259
1316- // Connection events should NOT be deleted by audit logs retention
1260+ // Connection events should NOT be deleted by audit logs retention.
13171261require .Contains (t ,logIDs ,oldConnectLog .ID ,"old connect log should NOT be deleted by audit logs retention" )
13181262require .Contains (t ,logIDs ,oldDisconnectLog .ID ,"old disconnect log should NOT be deleted by audit logs retention" )
13191263require .Contains (t ,logIDs ,oldOpenLog .ID ,"old open log should NOT be deleted by audit logs retention" )
13201264require .Contains (t ,logIDs ,oldCloseLog .ID ,"old close log should NOT be deleted by audit logs retention" )
13211265
1322- // Non-connection event should be deleted
1266+ // Non-connection event should be deleted.
13231267require .NotContains (t ,logIDs ,oldCreateLog .ID ,"old create log should be deleted by audit logs retention" )
13241268})
13251269}