blob: 1fbad09e63b106f98f2831d7add38914b0e99dc8 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package googlegroups
import (
"bytes"
"context"
"fmt"
"net/http"
"regexp"
"slices"
"strings"
"testing"
"golang.org/x/oscar/internal/httprr"
"golang.org/x/oscar/internal/secret"
"golang.org/x/oscar/internal/storage"
"golang.org/x/oscar/internal/testutil"
)
// aizaRegexp is used to identify AIza Cloud credentials
// in test data.
var aizaRegexp = regexp.MustCompile("AIza[0-9A-Za-z_-]{35}")
// scrub is a response scrubber for use with [rsc.io/httprr].
func scrub(b *bytes.Buffer) error {
body := b.String()
b.Reset()
newBody := aizaRegexp.ReplaceAllString(body, "")
_, err := b.WriteString(newBody)
return err
}
func TestSync(t *testing.T) {
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
ctx := context.Background()
rr, err := httprr.Open("testdata/sync.httprr", http.DefaultTransport)
check(err)
rr.ScrubResp(scrub)
sdb := secret.Empty()
c := New(lg, db, sdb, rr.Client())
const group = "golang-announce"
check(c.Add(group))
convs := func() []*Conversation {
var convs []*Conversation
for _, cfn := range c.Conversations(group) {
convs = append(convs, cfn())
}
return convs
}
// Only look at changes before a certain date, so that we
// don't get too much data.
testTomorrow = "2011-05-05"
defer func() { testTomorrow = "" }()
check(c.Sync(ctx))
cs := convs()
if len(cs) != 1 {
t.Errorf("got %d conversations; want 1", len(cs))
}
if cs[0].Title != "release.r57.1" {
t.Errorf("got %q as title; want 'release.r57.1'", cs[0].Title)
}
msgs := cs[0].Messages
if len(msgs) != 1 {
t.Errorf("got %d message; want 1", len(msgs))
}
const wantMessage = "just tagged our second stable Go release, release.r57.1."
if html := msgs[0]; !strings.Contains(html, wantMessage) {
t.Errorf("could not locate '%s' message in the %s message", wantMessage, cs[0].URL)
}
// Now go a little bit into the future, but there should
// be no more conversations.
testTomorrow = "2011-05-06"
check(c.Sync(ctx))
if got := len(convs()); got != 1 {
t.Errorf("got %d conversations; want 1", got)
}
}
func TestMatchConversationURL(t *testing.T) {
const group = "golang-dev"
for _, tc := range []struct {
url string
want bool
}{
{"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8", true},
{"groups.google.com/g/golang-dev/c/tXG6Jk_dos8", false},
{"https://groups.google.com/g/golang-nuts/c/tXG6Jk_dos8/m/my-groups", false}, // wrong group
{"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8/m/g/golang-dev", false},
} {
if got := matchesConversation(tc.url, group); got != tc.want {
t.Errorf("got match=%t for %s; want %t", got, tc.url, tc.want)
}
}
}
func TestConversationLink(t *testing.T) {
const group = "golang-dev"
for _, tc := range []struct {
url string
want string
}{
{"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8", // conversation
"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8"},
{"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8/m/1234", // conversation message
"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8"},
{"https://groups.google.com/g/golang-dev/g/golang-dev/c/tXG6Jk_dos8/m/1234", // repeated group
"https://groups.google.com/g/golang-dev/c/tXG6Jk_dos8"},
{"https://groups.google.com/g/golang-dev/about",
"https://groups.google.com/g/golang-dev/about"},
} {
if got := conversationLink(tc.url, group); got != tc.want {
t.Errorf("got %s for %s; want %s", got, tc.url, tc.want)
}
}
}
func TestSyncTesting(t *testing.T) {
check := testutil.Checker(t)
lg := testutil.Slogger(t)
ctx := context.Background()
group := "test"
numConvs := func(c *Client) int {
cnt := 0
for _, _ = range c.Conversations(group) {
cnt++
}
return cnt
}
for _, d := range []struct {
limit int
wantConvs int
wantInterrupt bool
}{
// interrupt is skipped since it should happen at the
// second conversation of the day, but the day limit is 1.
// Due to this limit, we sync only 1 conversation per day.
{1, 4, false},
// With increased limit, we can sync more conversations
// per day.
{2, 6, true},
{3, 7, true},
} {
t.Run(fmt.Sprintf("%d", d.limit), func(t *testing.T) {
db := storage.MemDB()
c := New(lg, db, nil, nil)
check(c.Add(group))
tc := c.Testing()
tc.setLimit(d.limit)
check(tc.LoadTxtar("testdata/interrupt_convs.txt"))
err := c.Sync(ctx)
if d.wantInterrupt {
if err == nil || !strings.Contains(err.Error(), "test interrupt error") {
t.Fatalf("want test interrupt error; got %v", err)
}
} else if err != nil {
t.Fatal(err)
}
check(c.Sync(ctx)) // repeat without interruption
if gotConvs := numConvs(c); gotConvs != d.wantConvs {
t.Errorf("want %d conversations; got %d", d.wantConvs, gotConvs)
}
})
}
}
func TestTitleAndMessages(t *testing.T) {
check := testutil.Checker(t)
lg := testutil.Slogger(t)
h := `<html>
<head></head>
<body>
<script>some javascript</script>
<div id="go">Go</div>
<div aria-label="golang-test">
<section><div id="section1">Section 1</div></section>
<div><section>Section 2</section></div>
<section>Section 3<h1><section id="section4">Section 4</section></h1></section>
</div>
</body>
</html>`
title, msgs, err := titleAndMessages(lg, []byte(h))
check(err)
if len(msgs) != 4 {
t.Errorf("got %d messages; want 4", len(msgs))
}
want := []string{
"<section><div>Section 1</div></section>",
"<section>Section 2</section>",
"<section>Section 3<h1><section>Section 4</section></h1></section>",
"<section>Section 4</section>",
}
if !slices.Equal(msgs, want) {
t.Errorf("got %v messages; want %v", msgs, want)
}
if title != "golang-test" {
t.Errorf("got %q as title,; want 'golang-test'", title)
}
}
func TestTruncate(t *testing.T) {
testDBSizeLimit = 120
defer func() { testDBSizeLimit = 0 }()
c := &Conversation{
URL: "https://groups.google.com/g/golang/test/c/123456",
Title: "convo",
Messages: []string{"open", "reply", "close"},
}
// Conversation c in JSON is 121 bytes big, so
// only the last message should be truncated.
if !truncate(c) {
t.Error("want conversation truncated")
}
want := []string{"open", "reply"}
if !slices.Equal(want, c.Messages) {
t.Errorf("got %v messages; want %v", c.Messages, want)
}
if truncate(c) {
t.Error("conversation truncation not idempotent")
}
}