201 lines
6.4 KiB
Go
201 lines
6.4 KiB
Go
|
|
package store
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"errors"
|
|||
|
|
"path/filepath"
|
|||
|
|
"testing"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"glchat/internal/database"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// newLinkPreviewTestStore открывает временную БД со всеми миграциями.
|
|||
|
|
// В пакете store это первый тест, поэтому помощник живёт здесь.
|
|||
|
|
func newLinkPreviewTestStore(t *testing.T) *Store {
|
|||
|
|
t.Helper()
|
|||
|
|
db, err := database.Open(context.Background(), database.Options{
|
|||
|
|
Path: filepath.Join(t.TempDir(), "glchat.db"),
|
|||
|
|
ReadPool: 2,
|
|||
|
|
Migrate: true,
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("open test database: %v", err)
|
|||
|
|
}
|
|||
|
|
t.Cleanup(func() {
|
|||
|
|
if err := db.Close(); err != nil {
|
|||
|
|
t.Errorf("close test database: %v", err)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
return New(db)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestLinkPreviewSaveAndGet(t *testing.T) {
|
|||
|
|
ctx := context.Background()
|
|||
|
|
st := newLinkPreviewTestStore(t)
|
|||
|
|
|
|||
|
|
fetchedAt := time.Now().UTC()
|
|||
|
|
params := SaveLinkPreviewParams{
|
|||
|
|
URLHash: "hash-1",
|
|||
|
|
URL: "https://example.com/page",
|
|||
|
|
Title: "Заголовок",
|
|||
|
|
Description: "Описание",
|
|||
|
|
SiteName: "Пример",
|
|||
|
|
ImageURL: "https://example.com/pic.png",
|
|||
|
|
Status: LinkPreviewStatusOK,
|
|||
|
|
FetchedAt: fetchedAt,
|
|||
|
|
ExpiresAt: fetchedAt.Add(time.Hour),
|
|||
|
|
}
|
|||
|
|
if err := st.SaveLinkPreview(ctx, params); err != nil {
|
|||
|
|
t.Fatalf("SaveLinkPreview() вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
preview, err := st.GetLinkPreview(ctx, params.URLHash)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("GetLinkPreview() вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
if preview.URLHash != params.URLHash || preview.URL != params.URL {
|
|||
|
|
t.Errorf("URLHash/URL = %q/%q, want %q/%q", preview.URLHash, preview.URL, params.URLHash, params.URL)
|
|||
|
|
}
|
|||
|
|
if preview.Title != params.Title || preview.Description != params.Description {
|
|||
|
|
t.Errorf("Title/Description = %q/%q", preview.Title, preview.Description)
|
|||
|
|
}
|
|||
|
|
if preview.SiteName != params.SiteName || preview.ImageURL != params.ImageURL {
|
|||
|
|
t.Errorf("SiteName/ImageURL = %q/%q", preview.SiteName, preview.ImageURL)
|
|||
|
|
}
|
|||
|
|
if preview.Status != LinkPreviewStatusOK {
|
|||
|
|
t.Errorf("Status = %q, want %q", preview.Status, LinkPreviewStatusOK)
|
|||
|
|
}
|
|||
|
|
if want := st.Timestamp(params.FetchedAt); preview.FetchedAt != want {
|
|||
|
|
t.Errorf("FetchedAt = %q, want %q", preview.FetchedAt, want)
|
|||
|
|
}
|
|||
|
|
if want := st.Timestamp(params.ExpiresAt); preview.ExpiresAt != want {
|
|||
|
|
t.Errorf("ExpiresAt = %q, want %q", preview.ExpiresAt, want)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestLinkPreviewUpsert(t *testing.T) {
|
|||
|
|
ctx := context.Background()
|
|||
|
|
st := newLinkPreviewTestStore(t)
|
|||
|
|
now := time.Now().UTC()
|
|||
|
|
|
|||
|
|
first := SaveLinkPreviewParams{
|
|||
|
|
URLHash: "hash-upsert",
|
|||
|
|
URL: "https://example.com/page",
|
|||
|
|
Title: "Старый заголовок",
|
|||
|
|
Status: LinkPreviewStatusEmpty,
|
|||
|
|
FetchedAt: now,
|
|||
|
|
ExpiresAt: now.Add(time.Hour),
|
|||
|
|
}
|
|||
|
|
if err := st.SaveLinkPreview(ctx, first); err != nil {
|
|||
|
|
t.Fatalf("SaveLinkPreview() вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
second := SaveLinkPreviewParams{
|
|||
|
|
URLHash: first.URLHash,
|
|||
|
|
URL: first.URL,
|
|||
|
|
Title: "Новый заголовок",
|
|||
|
|
Description: "Новое описание",
|
|||
|
|
ImageURL: "https://example.com/new.png",
|
|||
|
|
Status: LinkPreviewStatusOK,
|
|||
|
|
FetchedAt: now.Add(time.Minute),
|
|||
|
|
ExpiresAt: now.Add(2 * time.Hour),
|
|||
|
|
}
|
|||
|
|
if err := st.SaveLinkPreview(ctx, second); err != nil {
|
|||
|
|
t.Fatalf("SaveLinkPreview() при перезаписи вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
preview, err := st.GetLinkPreview(ctx, first.URLHash)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("GetLinkPreview() вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
if preview.Title != second.Title || preview.Description != second.Description {
|
|||
|
|
t.Errorf("после upsert Title/Description = %q/%q, want %q/%q",
|
|||
|
|
preview.Title, preview.Description, second.Title, second.Description)
|
|||
|
|
}
|
|||
|
|
if preview.Status != LinkPreviewStatusOK {
|
|||
|
|
t.Errorf("после upsert Status = %q, want %q", preview.Status, LinkPreviewStatusOK)
|
|||
|
|
}
|
|||
|
|
if want := st.Timestamp(second.ExpiresAt); preview.ExpiresAt != want {
|
|||
|
|
t.Errorf("после upsert ExpiresAt = %q, want %q", preview.ExpiresAt, want)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var count int
|
|||
|
|
if err := st.Reader().QueryRowContext(ctx,
|
|||
|
|
`SELECT COUNT(*) FROM link_previews WHERE url_hash = ?`, first.URLHash).Scan(&count); err != nil {
|
|||
|
|
t.Fatalf("подсчёт строк: %v", err)
|
|||
|
|
}
|
|||
|
|
if count != 1 {
|
|||
|
|
t.Errorf("строк с тем же url_hash = %d, want 1", count)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestLinkPreviewExpiredNotFound(t *testing.T) {
|
|||
|
|
ctx := context.Background()
|
|||
|
|
st := newLinkPreviewTestStore(t)
|
|||
|
|
now := time.Now().UTC()
|
|||
|
|
|
|||
|
|
params := SaveLinkPreviewParams{
|
|||
|
|
URLHash: "hash-expired",
|
|||
|
|
URL: "https://example.com/old",
|
|||
|
|
Title: "Просроченное",
|
|||
|
|
Status: LinkPreviewStatusOK,
|
|||
|
|
FetchedAt: now.Add(-2 * time.Hour),
|
|||
|
|
ExpiresAt: now.Add(-time.Hour),
|
|||
|
|
}
|
|||
|
|
if err := st.SaveLinkPreview(ctx, params); err != nil {
|
|||
|
|
t.Fatalf("SaveLinkPreview() вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if _, err := st.GetLinkPreview(ctx, params.URLHash); !errors.Is(err, ErrNotFound) {
|
|||
|
|
t.Fatalf("GetLinkPreview() err = %v, want ErrNotFound", err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestDeleteExpiredLinkPreviews(t *testing.T) {
|
|||
|
|
ctx := context.Background()
|
|||
|
|
st := newLinkPreviewTestStore(t)
|
|||
|
|
now := time.Now().UTC()
|
|||
|
|
|
|||
|
|
fresh := SaveLinkPreviewParams{
|
|||
|
|
URLHash: "hash-fresh",
|
|||
|
|
URL: "https://example.com/fresh",
|
|||
|
|
Title: "Свежее",
|
|||
|
|
Status: LinkPreviewStatusOK,
|
|||
|
|
FetchedAt: now,
|
|||
|
|
ExpiresAt: now.Add(time.Hour),
|
|||
|
|
}
|
|||
|
|
expired := SaveLinkPreviewParams{
|
|||
|
|
URLHash: "hash-old",
|
|||
|
|
URL: "https://example.com/old",
|
|||
|
|
Title: "Просроченное",
|
|||
|
|
Status: LinkPreviewStatusError,
|
|||
|
|
FetchedAt: now.Add(-2 * time.Hour),
|
|||
|
|
ExpiresAt: now.Add(-time.Hour),
|
|||
|
|
}
|
|||
|
|
for _, params := range []SaveLinkPreviewParams{fresh, expired} {
|
|||
|
|
if err := st.SaveLinkPreview(ctx, params); err != nil {
|
|||
|
|
t.Fatalf("SaveLinkPreview(%q) вернул ошибку: %v", params.URLHash, err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
deleted, err := st.DeleteExpiredLinkPreviews(ctx, now)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("DeleteExpiredLinkPreviews() вернул ошибку: %v", err)
|
|||
|
|
}
|
|||
|
|
if deleted != 1 {
|
|||
|
|
t.Errorf("удалено записей = %d, want 1", deleted)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var left int
|
|||
|
|
if err := st.Reader().QueryRowContext(ctx, `SELECT COUNT(*) FROM link_previews`).Scan(&left); err != nil {
|
|||
|
|
t.Fatalf("подсчёт строк: %v", err)
|
|||
|
|
}
|
|||
|
|
if left != 1 {
|
|||
|
|
t.Errorf("в таблице осталось %d записей, want 1", left)
|
|||
|
|
}
|
|||
|
|
if _, err := st.GetLinkPreview(ctx, fresh.URLHash); err != nil {
|
|||
|
|
t.Errorf("свежая запись недоступна: %v", err)
|
|||
|
|
}
|
|||
|
|
}
|