From c8a32ae0b0947beb3988a5be5fae41dd28f320c4 Mon Sep 17 00:00:00 2001 From: Ritesh Kumar Date: Fri, 15 May 2026 16:38:25 +0530 Subject: [PATCH 1/5] add audit events for document channel history compaction with test coverage # Conflicts: # rest/doc_api.go # Conflicts: # rest/doc_api.go --- base/audit_events.go | 53 ++++++++++++++++--- rest/audit_test.go | 119 +++++++++++++++++++++++++++++++++++++++++++ rest/doc_api.go | 9 +++- 3 files changed, 172 insertions(+), 9 deletions(-) diff --git a/base/audit_events.go b/base/audit_events.go index f16bd8f7ff..e0eef1fd3a 100644 --- a/base/audit_events.go +++ b/base/audit_events.go @@ -115,14 +115,17 @@ const ( AuditIDISGRAllRead AuditID = 54421 // Documents events - AuditIDDocumentCreate AuditID = 55000 - AuditIDDocumentRead AuditID = 55001 - AuditIDDocumentUpdate AuditID = 55002 - AuditIDDocumentDelete AuditID = 55003 - AuditIDDocumentMetadataRead AuditID = 55004 - AuditIDDocumentImport AuditID = 55005 - AuditIDDocumentResync AuditID = 55006 - AuditIDDocumentRevoke AuditID = 55007 + AuditIDDocumentCreate AuditID = 55000 + AuditIDDocumentRead AuditID = 55001 + AuditIDDocumentUpdate AuditID = 55002 + AuditIDDocumentDelete AuditID = 55003 + AuditIDDocumentMetadataRead AuditID = 55004 + AuditIDDocumentImport AuditID = 55005 + AuditIDDocumentResync AuditID = 55006 + AuditIDDocumentRevoke AuditID = 55007 + AuditIDDocumentChannelHistory AuditID = 55008 + AuditIDDocumentChannelHistoryCompact AuditID = 55009 + // Document attachments events AuditIDAttachmentCreate AuditID = 55010 AuditIDAttachmentRead AuditID = 55011 @@ -1193,6 +1196,40 @@ var AuditEvents = events{ FilteringPermitted: true, EventType: eventTypeData, }, + AuditIDDocumentChannelHistory: { + Name: "Document Channel history", + Description: "A document channel history was sent to a client", + MandatoryFields: AuditFields{ + AuditFieldDocID: "document id", + }, + mandatoryFieldGroups: []fieldGroup{ + fieldGroupAuthenticated, + fieldGroupKeyspace, + }, + optionalFieldGroups: []fieldGroup{ + fieldGroupRequest, + }, + EnabledByDefault: false, + FilteringPermitted: true, + EventType: eventTypeData, + }, + AuditIDDocumentChannelHistoryCompact: { + Name: "Document Channel history compact", + Description: "A document channel history was compacted by a client", + MandatoryFields: AuditFields{ + AuditFieldDocID: "document id", + }, + mandatoryFieldGroups: []fieldGroup{ + fieldGroupAuthenticated, + fieldGroupKeyspace, + }, + optionalFieldGroups: []fieldGroup{ + fieldGroupRequest, + }, + EnabledByDefault: false, + FilteringPermitted: true, + EventType: eventTypeData, + }, AuditIDAttachmentCreate: { Name: "Create attachment", Description: "A new attachment was created", diff --git a/rest/audit_test.go b/rest/audit_test.go index d4ddd7356b..c280461583 100644 --- a/rest/audit_test.go +++ b/rest/audit_test.go @@ -1967,6 +1967,124 @@ func TestDatabaseAuditChanges(t *testing.T) { } } +func TestDocumentChannelHistoryCompactionAudit(t *testing.T) { + rt := createAuditLoggingRestTester(t) + defer rt.Close() + + dbConfig := rt.NewDbConfig() + dbConfig.Logging = &DbLoggingConfig{ + Audit: &DbAuditLoggingConfig{ + Enabled: base.Ptr(true), + EnabledEvents: base.Ptr([]uint{ + uint(base.AuditIDDocumentChannelHistoryCompact), + }), + }, + } + RequireStatus(t, rt.CreateDatabase("db", dbConfig), http.StatusCreated) + + testCases := []struct { + name string + docIDs []string + }{ + { + name: "single doc", + docIDs: []string{"single_doc"}, + }, + { + name: "multiple docs", + docIDs: []string{"multi_doc_1", "multi_doc_2", "multi_doc_3"}, + }, + } + + for _, tc := range testCases { + rt.Run(tc.name, func(t *testing.T) { + for _, docID := range tc.docIDs { + rt.CreateTestDoc(docID) + } + + body := string(base.MustJSONMarshal(t, CompactDocChannelHistoryRequest{ + DocIds: tc.docIDs, + Seq: 1, + })) + output := base.AuditLogContents(t, func(t testing.TB) { + RequireStatus(t, rt.SendAdminRequest(http.MethodPost, "/{{.keyspace}}/_channel_history/_compact", body), http.StatusOK) + }) + + for _, docID := range tc.docIDs { + requireChannelHistoryCompactAuditEvent(t, output, docID) + } + }) + } +} + +func TestDocumentChannelHistoryAudit(t *testing.T) { + rt := createAuditLoggingRestTester(t) + defer rt.Close() + + dbConfig := rt.NewDbConfig() + dbConfig.Logging = &DbLoggingConfig{ + Audit: &DbAuditLoggingConfig{ + Enabled: base.Ptr(true), + EnabledEvents: base.Ptr([]uint{ + uint(base.AuditIDDocumentChannelHistory), + }), + }, + } + RequireStatus(t, rt.CreateDatabase("db", dbConfig), http.StatusCreated) + + testCases := []struct { + name string + docID string + }{ + { + name: "existing doc", + docID: "existing_doc", + }, + } + + for _, tc := range testCases { + rt.Run(tc.name, func(t *testing.T) { + rt.CreateTestDoc(tc.docID) + + output := base.AuditLogContents(t, func(t testing.TB) { + RequireStatus(t, rt.SendAdminRequest(http.MethodGet, "/{{.keyspace}}/_channel_history/"+tc.docID, ""), http.StatusOK) + }) + + requireChannelHistoryAuditEvent(t, output, tc.docID) + }) + } +} + +// requireChannelHistoryCompactAuditEvent asserts that exactly one AuditIDDocumentChannelHistoryCompact event +// was logged for the given docID. +func requireChannelHistoryCompactAuditEvent(t testing.TB, output []byte, docID string) { + t.Helper() + requireDocChannelAuditEvent(t, output, base.AuditIDDocumentChannelHistoryCompact, docID) +} + +// requireChannelHistoryAuditEvent asserts that exactly one AuditIDDocumentChannelHistory event +// was logged for the given docID. +func requireChannelHistoryAuditEvent(t testing.TB, output []byte, docID string) { + t.Helper() + requireDocChannelAuditEvent(t, output, base.AuditIDDocumentChannelHistory, docID) +} + +func requireDocChannelAuditEvent(t testing.TB, output []byte, eventID base.AuditID, docID string) { + t.Helper() + events := jsonLines(t, output) + countFound := 0 + for _, event := range events { + if base.AuditID(event[base.AuditFieldID].(float64)) != eventID { + continue + } + if event[base.AuditFieldDocID] == docID { + countFound++ + } + } + require.Equal(t, 1, countFound, "expected exactly 1 %s event for doc %q, got %d", + base.AuditEvents[eventID].Name, docID, countFound) +} + // getAuditLoggingTestConfig returns a logging config with audit enabled and other loggers configured without collation to avoid CBG-4129 func getAuditLoggingTestConfig(tempdir string) base.LoggingConfig { return base.LoggingConfig{ @@ -1996,3 +2114,4 @@ func getAuditLoggingTestConfig(tempdir string) base.LoggingConfig { }, } } + diff --git a/rest/doc_api.go b/rest/doc_api.go index 67cb2f9fd8..7e11dcecf7 100644 --- a/rest/doc_api.go +++ b/rest/doc_api.go @@ -941,6 +941,10 @@ func (h *handler) handleGetDocChannelHistory() error { return err } + base.Audit(h.ctx(), base.AuditIDDocumentChannelHistory, base.AuditFields{ + base.AuditFieldDocID: docid, + }) + h.writeJSON(chanHistory) return nil } @@ -975,7 +979,10 @@ func (h *handler) handleCompactDocChannelHistory() error { } res := map[string][]string{ "compacted_channels": channels, - } + } + base.Audit(h.ctx(), base.AuditIDDocumentChannelHistoryCompact, base.AuditFields{ + base.AuditFieldDocID: docid, + }) h.writeJSON(res) return nil From 22a5ae3f0d982ac63fe2fc83cee6622989efaa1c Mon Sep 17 00:00:00 2001 From: Ritesh Kumar Date: Fri, 15 May 2026 16:45:03 +0530 Subject: [PATCH 2/5] lint fix --- rest/audit_test.go | 1 - rest/doc_api.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rest/audit_test.go b/rest/audit_test.go index c280461583..08eadbe667 100644 --- a/rest/audit_test.go +++ b/rest/audit_test.go @@ -2114,4 +2114,3 @@ func getAuditLoggingTestConfig(tempdir string) base.LoggingConfig { }, } } - diff --git a/rest/doc_api.go b/rest/doc_api.go index 7e11dcecf7..e2babe14e3 100644 --- a/rest/doc_api.go +++ b/rest/doc_api.go @@ -979,7 +979,7 @@ func (h *handler) handleCompactDocChannelHistory() error { } res := map[string][]string{ "compacted_channels": channels, - } + } base.Audit(h.ctx(), base.AuditIDDocumentChannelHistoryCompact, base.AuditFields{ base.AuditFieldDocID: docid, }) From f43007946d9c78551b8b1fe5690453c25b837eaf Mon Sep 17 00:00:00 2001 From: Ritesh Kumar Date: Tue, 19 May 2026 18:55:47 +0530 Subject: [PATCH 3/5] fixes based on pr comments - replaced audit event for reading channel history with document metadata read - updated description of the event - added channels and sequence into the audit event for document channel history compaction # Conflicts: # base/audit_events_fields.go --- base/audit_events.go | 26 ++++-------------- base/audit_events_fields.go | 1 + rest/audit_test.go | 54 +------------------------------------ rest/doc_api.go | 7 +++-- 4 files changed, 12 insertions(+), 76 deletions(-) diff --git a/base/audit_events.go b/base/audit_events.go index e0eef1fd3a..79ce4a0d7b 100644 --- a/base/audit_events.go +++ b/base/audit_events.go @@ -123,8 +123,7 @@ const ( AuditIDDocumentImport AuditID = 55005 AuditIDDocumentResync AuditID = 55006 AuditIDDocumentRevoke AuditID = 55007 - AuditIDDocumentChannelHistory AuditID = 55008 - AuditIDDocumentChannelHistoryCompact AuditID = 55009 + AuditIDDocumentChannelHistoryCompact AuditID = 55008 // Document attachments events AuditIDAttachmentCreate AuditID = 55010 @@ -1196,28 +1195,13 @@ var AuditEvents = events{ FilteringPermitted: true, EventType: eventTypeData, }, - AuditIDDocumentChannelHistory: { - Name: "Document Channel history", - Description: "A document channel history was sent to a client", - MandatoryFields: AuditFields{ - AuditFieldDocID: "document id", - }, - mandatoryFieldGroups: []fieldGroup{ - fieldGroupAuthenticated, - fieldGroupKeyspace, - }, - optionalFieldGroups: []fieldGroup{ - fieldGroupRequest, - }, - EnabledByDefault: false, - FilteringPermitted: true, - EventType: eventTypeData, - }, AuditIDDocumentChannelHistoryCompact: { Name: "Document Channel history compact", - Description: "A document channel history was compacted by a client", + Description: "A document channel history was compacted by the Administrator", MandatoryFields: AuditFields{ - AuditFieldDocID: "document id", + AuditFieldDocID: "document id", + AuditFieldChannels: "channels", + AuditFieldSequence: "sequence", }, mandatoryFieldGroups: []fieldGroup{ fieldGroupAuthenticated, diff --git a/base/audit_events_fields.go b/base/audit_events_fields.go index efea3cad48..8a1e1e3cc4 100644 --- a/base/audit_events_fields.go +++ b/base/audit_events_fields.go @@ -90,4 +90,5 @@ const ( // Cluster compat version freeze events 53352, 53353 AuditFieldClusterCompatVersion = "cluster_compat_version" AuditFieldFrozenAt = "frozen_at" + AuditFieldSequence = "seq" ) diff --git a/rest/audit_test.go b/rest/audit_test.go index 08eadbe667..23a62e5a69 100644 --- a/rest/audit_test.go +++ b/rest/audit_test.go @@ -2011,64 +2011,12 @@ func TestDocumentChannelHistoryCompactionAudit(t *testing.T) { }) for _, docID := range tc.docIDs { - requireChannelHistoryCompactAuditEvent(t, output, docID) + requireDocChannelAuditEvent(t, output, base.AuditIDDocumentChannelHistoryCompact, docID) } }) } } -func TestDocumentChannelHistoryAudit(t *testing.T) { - rt := createAuditLoggingRestTester(t) - defer rt.Close() - - dbConfig := rt.NewDbConfig() - dbConfig.Logging = &DbLoggingConfig{ - Audit: &DbAuditLoggingConfig{ - Enabled: base.Ptr(true), - EnabledEvents: base.Ptr([]uint{ - uint(base.AuditIDDocumentChannelHistory), - }), - }, - } - RequireStatus(t, rt.CreateDatabase("db", dbConfig), http.StatusCreated) - - testCases := []struct { - name string - docID string - }{ - { - name: "existing doc", - docID: "existing_doc", - }, - } - - for _, tc := range testCases { - rt.Run(tc.name, func(t *testing.T) { - rt.CreateTestDoc(tc.docID) - - output := base.AuditLogContents(t, func(t testing.TB) { - RequireStatus(t, rt.SendAdminRequest(http.MethodGet, "/{{.keyspace}}/_channel_history/"+tc.docID, ""), http.StatusOK) - }) - - requireChannelHistoryAuditEvent(t, output, tc.docID) - }) - } -} - -// requireChannelHistoryCompactAuditEvent asserts that exactly one AuditIDDocumentChannelHistoryCompact event -// was logged for the given docID. -func requireChannelHistoryCompactAuditEvent(t testing.TB, output []byte, docID string) { - t.Helper() - requireDocChannelAuditEvent(t, output, base.AuditIDDocumentChannelHistoryCompact, docID) -} - -// requireChannelHistoryAuditEvent asserts that exactly one AuditIDDocumentChannelHistory event -// was logged for the given docID. -func requireChannelHistoryAuditEvent(t testing.TB, output []byte, docID string) { - t.Helper() - requireDocChannelAuditEvent(t, output, base.AuditIDDocumentChannelHistory, docID) -} - func requireDocChannelAuditEvent(t testing.TB, output []byte, eventID base.AuditID, docID string) { t.Helper() events := jsonLines(t, output) diff --git a/rest/doc_api.go b/rest/doc_api.go index e2babe14e3..503358cd8e 100644 --- a/rest/doc_api.go +++ b/rest/doc_api.go @@ -941,7 +941,7 @@ func (h *handler) handleGetDocChannelHistory() error { return err } - base.Audit(h.ctx(), base.AuditIDDocumentChannelHistory, base.AuditFields{ + base.Audit(h.ctx(), base.AuditIDDocumentMetadataRead, base.AuditFields{ base.AuditFieldDocID: docid, }) @@ -980,8 +980,11 @@ func (h *handler) handleCompactDocChannelHistory() error { res := map[string][]string{ "compacted_channels": channels, } + base.Audit(h.ctx(), base.AuditIDDocumentChannelHistoryCompact, base.AuditFields{ - base.AuditFieldDocID: docid, + base.AuditFieldDocID: docid, + base.AuditFieldChannels: channels, + base.AuditFieldSequence: req.Seq, }) h.writeJSON(res) From f88c31b694e4643057844cfa094c0da422129ccb Mon Sep 17 00:00:00 2001 From: Ritesh Kumar Date: Thu, 21 May 2026 18:13:53 +0530 Subject: [PATCH 4/5] edit audit event field types --- base/audit_events.go | 2 +- rest/audit_test.go | 7 ++++--- rest/doc_api.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/base/audit_events.go b/base/audit_events.go index 79ce4a0d7b..4011683411 100644 --- a/base/audit_events.go +++ b/base/audit_events.go @@ -1200,7 +1200,7 @@ var AuditEvents = events{ Description: "A document channel history was compacted by the Administrator", MandatoryFields: AuditFields{ AuditFieldDocID: "document id", - AuditFieldChannels: "channels", + AuditFieldChannels: []string{"list", "of", "channels"}, AuditFieldSequence: "sequence", }, mandatoryFieldGroups: []fieldGroup{ diff --git a/rest/audit_test.go b/rest/audit_test.go index 23a62e5a69..f6b4c78f68 100644 --- a/rest/audit_test.go +++ b/rest/audit_test.go @@ -2003,11 +2003,12 @@ func TestDocumentChannelHistoryCompactionAudit(t *testing.T) { } body := string(base.MustJSONMarshal(t, CompactDocChannelHistoryRequest{ - DocIds: tc.docIDs, - Seq: 1, + Seq: 1, })) output := base.AuditLogContents(t, func(t testing.TB) { - RequireStatus(t, rt.SendAdminRequest(http.MethodPost, "/{{.keyspace}}/_channel_history/_compact", body), http.StatusOK) + for _, docID := range tc.docIDs { + RequireStatus(t, rt.SendAdminRequest(http.MethodPost, "/{{.keyspace}}/_channel_history/"+docID+"/compact", body), http.StatusOK) + } }) for _, docID := range tc.docIDs { diff --git a/rest/doc_api.go b/rest/doc_api.go index 503358cd8e..3c7118dcf4 100644 --- a/rest/doc_api.go +++ b/rest/doc_api.go @@ -984,7 +984,7 @@ func (h *handler) handleCompactDocChannelHistory() error { base.Audit(h.ctx(), base.AuditIDDocumentChannelHistoryCompact, base.AuditFields{ base.AuditFieldDocID: docid, base.AuditFieldChannels: channels, - base.AuditFieldSequence: req.Seq, + base.AuditFieldSequence: strconv.FormatUint(req.Seq, 10), }) h.writeJSON(res) From f8773c5c24f1fcc44db86e973e2e7dda6a7267cd Mon Sep 17 00:00:00 2001 From: Ritesh Kumar Date: Wed, 27 May 2026 18:37:34 +0530 Subject: [PATCH 5/5] lint fix --- base/audit_events_fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/audit_events_fields.go b/base/audit_events_fields.go index 8a1e1e3cc4..ba1d6eea7c 100644 --- a/base/audit_events_fields.go +++ b/base/audit_events_fields.go @@ -86,9 +86,9 @@ const ( AuditFieldDocIDs = "doc_ids" AuditFieldFeedType = "feed_type" AuditFieldIncludeDocs = "include_docs" + AuditFieldSequence = "seq" // Cluster compat version freeze events 53352, 53353 AuditFieldClusterCompatVersion = "cluster_compat_version" AuditFieldFrozenAt = "frozen_at" - AuditFieldSequence = "seq" )