package store import ( "context" "errors" "os" "testing" "time" "github.com/jmoiron/sqlx" ) func TestPendingNoticeAudienceAndReadAcknowledgement(t *testing.T) { dsn := os.Getenv("OLONS_TEST_MYSQL_DSN") if dsn == "" { t.Skip("set OLONS_TEST_MYSQL_DSN to run the notice integration test") } db, err := sqlx.Connect("mysql", dsn) if err != nil { t.Fatal(err) } db.SetMaxOpenConns(1) db.SetMaxIdleConns(1) t.Cleanup(func() { _ = db.Close() }) // Connection-local temporary tables shadow the real tables without changing application data. for _, statement := range []string{ `CREATE TEMPORARY TABLE hg_app_member_notice ( id BIGINT PRIMARY KEY, tenant_id BIGINT, member_id BIGINT, title VARCHAR(120), content TEXT, source_type INT, notice_status INT, start_at DATETIME(3), end_at DATETIME(3), created_at DATETIME(3))`, `CREATE TEMPORARY TABLE hg_app_member_notice_read ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT, notice_id BIGINT, member_id BIGINT, read_at DATETIME(3), created_at DATETIME(3), UNIQUE KEY (tenant_id, notice_id, member_id))`, } { if _, err := db.Exec(statement); err != nil { t.Fatal(err) } } now := time.Now() for _, notice := range []struct { id, tenantID, memberID int64 status int start, end time.Time }{ {1, 0, 9, 1, now.Add(-2 * time.Hour), now.Add(time.Hour)}, {2, 0, 9, 1, now.Add(-time.Hour), now.Add(time.Hour)}, {3, 0, 10, 1, now.Add(-time.Hour), now.Add(time.Hour)}, {4, 1, 9, 1, now.Add(-time.Hour), now.Add(time.Hour)}, {5, 0, 0, 1, now.Add(-time.Hour), now.Add(time.Hour)}, {6, 0, 9, 2, now.Add(-time.Hour), now.Add(time.Hour)}, {7, 0, 9, 1, now.Add(-2 * time.Hour), now.Add(-time.Hour)}, {8, 0, 9, 1, now.Add(time.Hour), now.Add(2 * time.Hour)}, } { _, err := db.Exec(`INSERT INTO hg_app_member_notice (id,tenant_id,member_id,title,content,source_type,notice_status,start_at,end_at,created_at) VALUES (?,?,?,'系统通知','通知内容',1,?,?,?,?)`, notice.id, notice.tenantID, notice.memberID, notice.status, formatNoticeBusinessTime(notice.start), formatNoticeBusinessTime(notice.end), formatNoticeBusinessTime(now)) if err != nil { t.Fatal(err) } } ctx := context.Background() s := &Store{db: db} for _, expectedID := range []int64{1, 2} { notice, err := s.NextUnreadMemberNotice(ctx, 0, 9) if err != nil || notice == nil || notice.ID != expectedID { t.Fatalf("pending notice = %+v, error = %v, want id %d", notice, err, expectedID) } if err := s.MarkMemberNoticeRead(ctx, 0, 9, expectedID); err != nil { t.Fatal(err) } } if notice, err := s.NextUnreadMemberNotice(ctx, 0, 9); err != nil || notice != nil { t.Fatalf("read, expired, revoked, foreign and broadcast notices must not pop up: %+v, %v", notice, err) } for _, forbiddenID := range []int64{3, 4} { if err := s.MarkMemberNoticeRead(ctx, 0, 9, forbiddenID); !errors.Is(err, ErrResourceNotFound) { t.Fatalf("reading foreign notice %d: error = %v", forbiddenID, err) } } if notice, err := s.NextUnreadMemberNotice(ctx, 0, 10); err != nil || notice == nil || notice.ID != 3 { t.Fatalf("other recipient must retain their unread notice: %+v, %v", notice, err) } }