Move audit subscribe to client, Fix tests

This commit is contained in:
George Suntres
2026-04-24 15:51:08 -04:00
parent d673df0ca1
commit 66ba2b8874
8 changed files with 128 additions and 30 deletions

View File

@@ -6,12 +6,11 @@ import (
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
"git.gsuntres.com/general/mongo/options"
"git.gsuntres.com/general/commons" "git.gsuntres.com/general/commons"
) )
// DeleteOne will delete the first document that matches the filter. // DeleteOne will delete the first document that matches the filter.
func (c *MongoClient) DeleteOne(ctx context.Context, database, name string, filter bson.M, opts ...options.Lister[options.DeleteOneOptions]) error { func (c *MongoClient) DeleteOne(ctx context.Context, database, name string, filter bson.M) error {
var err error var err error
// 1. Prepare query. // 1. Prepare query.

View File

@@ -10,7 +10,6 @@ import (
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
"git.gsuntres.com/general/commons" "git.gsuntres.com/general/commons"
"git.gsuntres.com/general/events"
) )
func TestDeleteOne(t *testing.T) { func TestDeleteOne(t *testing.T) {
@@ -124,21 +123,23 @@ func TestDeleteOne_WithAudit(t *testing.T) {
onAudit_before any onAudit_before any
onAudit_context any onAudit_context any
) )
events.Subscribe(func(audit *AuditResult) error { cancel := client.Subscribe(func(audit *AuditResult) error {
onAudit_calls++ onAudit_calls++
onAudit_before = audit.Before onAudit_before = audit.Before
onAudit_context = audit.Context onAudit_context = audit.Context
return nil return nil
}) })
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "account", "xxxxxx") ctx = context.WithValue(ctx, "account", "xxxxxx")
ctx = context.WithValue(ctx, "store", "str_4321") ctx = context.WithValue(ctx, "store", "str_4321")
err = client.DeleteOne(ctx, "mydb", "mycollection", bson.M{"_id": o["_id"].(string)}) err = client.DeleteOne(ctx, "mydb", "mycollection", bson.M{"_id": o["_id"].(string)})
if err != nil { t.Fatalf("Failed to deleteOne %#v", err) } if err != nil { t.Fatalf("Failed to deleteOne %#v", err) }
cancel()
// raw query // raw query
var results bson.M var results bson.M
filter := bson.M{ "name": "MyName" } filter := bson.M{ "name": "MyName" }
@@ -149,7 +150,7 @@ func TestDeleteOne_WithAudit(t *testing.T) {
if results != nil { if results != nil {
t.Fatalf("Should have no results not %v", results) t.Fatalf("Should have no results not %v", results)
} }
if onAudit_calls != 1 { if onAudit_calls != 1 {
t.Fatalf("ondelete should have been called once, not %d", onAudit_calls) t.Fatalf("ondelete should have been called once, not %d", onAudit_calls)
} }
@@ -173,7 +174,7 @@ func TestDeleteOne_WithAudit(t *testing.T) {
func TestDeleteOne_WithAuditButIgnored(t *testing.T) { func TestDeleteOne_WithAuditButIgnored(t *testing.T) {
client := GetMongoClient() client := GetMongoClient()
client.IgnoredAudit = []string{"mycollection"} client.IgnoreAudit = []string{"mycollection"}
data := map[string]any { data := map[string]any {
"_id": "su_123458", "_id": "su_123458",
@@ -189,22 +190,23 @@ func TestDeleteOne_WithAuditButIgnored(t *testing.T) {
var ( var (
onAudit_calls int onAudit_calls int
onAudit_before any onAudit_before any
onAudit_context any
) )
events.Subscribe(func(audit *AuditResult) error { cancel := client.Subscribe(func(audit *AuditResult) error {
onAudit_calls++ onAudit_calls++
onAudit_before = audit.Before onAudit_before = audit.Before
onAudit_context = audit.Context
return nil return nil
}) })
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "account", "xxxxxx") ctx = context.WithValue(ctx, "account", "xxxxxx")
ctx = context.WithValue(ctx, "store", "str_4321") ctx = context.WithValue(ctx, "store", "str_4321")
err = client.DeleteOne(ctx, "mydb", "mycollection", bson.M{"_id": o["_id"].(string)}) err = client.DeleteOne(ctx, "mydb", "mycollection", bson.M{"_id": o["_id"].(string)})
if err != nil { t.Fatalf("Failed to deleteOne %#v", err) } if err != nil { t.Fatalf("Failed to deleteOne %#v", err) }
cancel()
client.IgnoreAudit = []string{"event"}
// raw query // raw query
var results bson.M var results bson.M
@@ -217,8 +219,8 @@ func TestDeleteOne_WithAuditButIgnored(t *testing.T) {
t.Fatalf("Should have no results not %v", results) t.Fatalf("Should have no results not %v", results)
} }
if onAudit_calls != 1 { if onAudit_calls != 0 {
t.Fatalf("ondelete should have been called once, not %d", onAudit_calls) t.Fatalf("ondelete should have not been called. [%d]", onAudit_calls)
} }
if onAudit_before != nil { if onAudit_before != nil {

View File

@@ -100,7 +100,7 @@ func mapToOp(v string) string {
case "includes": case "includes":
return "$regex" return "$regex"
default: default:
log.Printf("WARN: no conversion") log.Printf("WARN: no conversion for %v", v)
return v return v
} }
} }

6
go.mod
View File

@@ -3,8 +3,8 @@ module git.gsuntres.com/general/mongo
go 1.25.0 go 1.25.0
require ( require (
git.gsuntres.com/general/commons v0.0.0-20260423171748-0ce3f3b5eb8c git.gsuntres.com/general/commons v0.0.0-20260423193720-28dcfb9e4ce9
git.gsuntres.com/general/events v0.0.0-20260423140000-1435849fb2c0 git.gsuntres.com/general/events v0.0.0-20260424194951-506e91ff46a2
git.gsuntres.com/general/sys v0.0.1 git.gsuntres.com/general/sys v0.0.1
github.com/go-viper/mapstructure/v2 v2.5.0 github.com/go-viper/mapstructure/v2 v2.5.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
@@ -78,3 +78,5 @@ require (
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
replace git.gsuntres.com/general/events => /home/master/Workspace/_general/events

14
go.sum
View File

@@ -1,11 +1,13 @@
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
git.gsuntres.com/general/commons v0.0.0-20260422214453-1e9e43668f5e h1:UFlnWT0u8ywStd7VRPNHmooZw3bSdXqhe/MwOpgWmvI= git.gsuntres.com/general/commons v0.0.0-20260423193720-28dcfb9e4ce9 h1:wbyyIEMTQqnfE/AfYlzpSwtqbfUW5RP9F6Tt84S7RMI=
git.gsuntres.com/general/commons v0.0.0-20260422214453-1e9e43668f5e/go.mod h1:s774W5vN/53DLYKeY4iwFnwPOIHfSg8/V6Ft7sEdl9M= git.gsuntres.com/general/commons v0.0.0-20260423193720-28dcfb9e4ce9/go.mod h1:S93xcBczrgN+gZU0JWkPRnTcAMvQuTp1ChyKkOd/I50=
git.gsuntres.com/general/commons v0.0.0-20260423171748-0ce3f3b5eb8c h1:YZJWYDqqUC0x687yYrcCYzMIvSO40H25IsInBCl+g6A= git.gsuntres.com/general/events v0.0.0-20260424123722-6d275bca7319 h1:HB3q3hrUOrfREV6eJLIZa++QEyw9i0l3FAGkhRqb5Xs=
git.gsuntres.com/general/commons v0.0.0-20260423171748-0ce3f3b5eb8c/go.mod h1:S93xcBczrgN+gZU0JWkPRnTcAMvQuTp1ChyKkOd/I50= git.gsuntres.com/general/events v0.0.0-20260424123722-6d275bca7319/go.mod h1:IQEt0/YT7vYHLTmRhjpqdHgHhagIHZNaay83fndui7s=
git.gsuntres.com/general/events v0.0.0-20260423140000-1435849fb2c0 h1:j4qqM1K6BMIG9ydzbuAljcAeaR/XbImEfs7GF6jtHlY= git.gsuntres.com/general/events v0.0.0-20260424131043-d6c514c3c113 h1:snNU8UgQ50E1Ud3+hGSJzMZG+Y2ILbcfyF+DEdKDVP0=
git.gsuntres.com/general/events v0.0.0-20260423140000-1435849fb2c0/go.mod h1:IQEt0/YT7vYHLTmRhjpqdHgHhagIHZNaay83fndui7s= git.gsuntres.com/general/events v0.0.0-20260424131043-d6c514c3c113/go.mod h1:IQEt0/YT7vYHLTmRhjpqdHgHhagIHZNaay83fndui7s=
git.gsuntres.com/general/events v0.0.0-20260424131248-c1c5eac835df h1:Ouu8cSqVKJAiJTmyvxeJm6gRjQ7IAhgdbkXDx0SnnHM=
git.gsuntres.com/general/events v0.0.0-20260424131248-c1c5eac835df/go.mod h1:IQEt0/YT7vYHLTmRhjpqdHgHhagIHZNaay83fndui7s=
git.gsuntres.com/general/sys v0.0.1 h1:JpGG6HCkJrTaCICR09kURhMTIc+/s8yb0lHQjo/TDVI= git.gsuntres.com/general/sys v0.0.1 h1:JpGG6HCkJrTaCICR09kURhMTIc+/s8yb0lHQjo/TDVI=
git.gsuntres.com/general/sys v0.0.1/go.mod h1:OVs7w4/tJO1GT7cLIeEsb90LuZqH2xYIVQODI5P1GJs= git.gsuntres.com/general/sys v0.0.1/go.mod h1:OVs7w4/tJO1GT7cLIeEsb90LuZqH2xYIVQODI5P1GJs=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=

View File

@@ -2,8 +2,11 @@ package mongo
import ( import (
"context" "context"
"slices"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
"git.gsuntres.com/general/commons"
) )
@@ -18,9 +21,11 @@ func (c *MongoClient) InsertOneFromStruct(ctx context.Context, database, name st
} }
// InsertOne will add missing ids and the created date before saving to the database. // InsertOne will add missing ids and the created date before saving to the database.
func (c *MongoClient) InsertOne(ctx context.Context, database, name string, data bson.M) (bson.M, error) { func (c *MongoClient) InsertOne(ctx context.Context, database, name string, original bson.M) (bson.M, error) {
collection := c.GetCollection(database, name) collection := c.GetCollection(database, name)
data := commons.BsonClone(original)
prepareForInsert(data, c.GetIdPrefix(name)) prepareForInsert(data, c.GetIdPrefix(name))
if err := c.DiscriminatorCheckAndApplyToData(ctx, name, data); err != nil { if err := c.DiscriminatorCheckAndApplyToData(ctx, name, data); err != nil {
@@ -30,6 +35,21 @@ func (c *MongoClient) InsertOne(ctx context.Context, database, name string, data
if _, err := collection.InsertOne(ctx, data); err != nil { if _, err := collection.InsertOne(ctx, data); err != nil {
return nil, err return nil, err
} }
ignoreAudit := slices.Contains(c.IgnoreAudit, name)
if c.WithAudit && !ignoreAudit {
contx := commons.ContextSerialize(ctx, c.ContextFields)
audit := &AuditResult {
Entity: name,
Op: OpInsert,
Data: original,
After: data,
Context: contx,
}
(*c.OnAudit)(audit)
}
return data, nil return data, nil
} }
@@ -42,5 +62,3 @@ func prepareForInsert(data bson.M, idPrefix string) {
ensureCreatedAt(data) ensureCreatedAt(data)
ensureUpdatedAt(data) ensureUpdatedAt(data)
} }

View File

@@ -2,6 +2,7 @@ package mongo
import ( import (
"os" "os"
"fmt"
"context" "context"
"strings" "strings"
"testing" "testing"
@@ -235,4 +236,72 @@ func TestInsertOne_Discriminate_EnsureStore(t *testing.T) {
if saved["store"] != "str_1234" { if saved["store"] != "str_1234" {
t.Fatal("Wrong store") t.Fatal("Wrong store")
} }
}
func TestInsertOne_WithAudit(t *testing.T) {
client := GetMongoClient()
var (
onAudit_calls int
onAudit_after any
onAudit_context any
)
cancel := client.Subscribe(func(audit *AuditResult) error {
onAudit_calls++
onAudit_after = audit.After
onAudit_context = audit.Context
return nil
})
data := map[string]any {
"_id": "su_123458",
"name": "MyNameTODelete",
"age": int32(25),
}
o, err := client.InsertOne(context.Background(), "mydb", "mycollection", data)
if err != nil { t.Fatalf("Failed to insertOne %#v", err) }
cancel()
ctx := context.Background()
ctx = context.WithValue(ctx, "account", "xxxxxx")
ctx = context.WithValue(ctx, "store", "str_4321")
err = client.DeleteOne(ctx, "mydb", "mycollection", bson.M{"_id": o["_id"].(string)})
if err != nil { t.Fatalf("Failed to deleteOne %#v", err) }
// raw query
var results bson.M
filter := bson.M{ "name": "MyNameTODelete" }
c := client.Client.Database("mydb").Collection("mycollection")
c.FindOne(context.Background(), filter).Decode(&results)
if results != nil {
t.Fatalf("Should have no results not %v", results)
}
if onAudit_calls != 1 {
t.Fatalf("ondelete should have been called once, not %d", onAudit_calls)
}
if onAudit_after != nil {
tp := fmt.Sprintf("%T", onAudit_after)
expectedType := fmt.Sprintf("%T", map[string]any{})
if tp != expectedType {
t.Fatalf("after has the wrong type %s", tp)
}
after := onAudit_after.(map[string]any)
AssertSubset(t, after, o, "Should have been equal")
}
if onAudit_context != nil {
ctx := onAudit_context.(map[string]any)
AssertSubset(t, ctx, map[string]any{"account": "xxxxxx", "store": "str_4321"}, "Should have been equal")
}
} }

10
main.go
View File

@@ -94,6 +94,8 @@ type MongoClient struct {
WithAudit bool WithAudit bool
// OnAudit callback called when audit is enabled. // OnAudit callback called when audit is enabled.
OnAudit *OnAudit OnAudit *OnAudit
// EventBus
EventBus *events.TypedEventBus
// IgnoreAudit a list of entities to ignore // IgnoreAudit a list of entities to ignore
IgnoreAudit []string IgnoreAudit []string
} }
@@ -364,11 +366,11 @@ func Start(props *MongoStartProps) error {
client.WithAudit = props.WithAudit client.WithAudit = props.WithAudit
client.OnAudit = props.OnAudit client.EventBus = events.NewTypedEventBus()
if client.WithAudit { if client.WithAudit {
var onAudit OnAudit = func(audit *AuditResult) error { var onAudit OnAudit = func(audit *AuditResult) error {
return events.Publish(audit) return events.Publish(client.EventBus, audit)
} }
client.OnAudit = &onAudit client.OnAudit = &onAudit
} }
@@ -402,6 +404,10 @@ func Start(props *MongoStartProps) error {
return nil return nil
} }
func (c *MongoClient) Subscribe(h events.Handler[AuditResult]) (cancel func()) {
return events.Subscribe(c.EventBus, h)
}
func GetClient() *mongo.Client { func GetClient() *mongo.Client {
return client.Client return client.Client
} }