2025-01-16 22:53:04 +00:00
|
|
|
package core
|
2025-01-16 19:46:37 +00:00
|
|
|
|
|
|
|
import (
|
2025-01-16 21:46:30 +00:00
|
|
|
"fmt"
|
|
|
|
"slices"
|
2025-01-16 19:46:37 +00:00
|
|
|
"testing"
|
2025-01-24 23:41:52 +00:00
|
|
|
"time"
|
2025-01-16 19:46:37 +00:00
|
|
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
)
|
|
|
|
|
2025-01-16 21:46:30 +00:00
|
|
|
func TestCreateSource(t *testing.T) {
|
|
|
|
db := EphemeralDb(t)
|
|
|
|
|
|
|
|
if err := AddSource(db, "one"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := AddSource(db, "two"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := AddSource(db, "three"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := DeleteSource(db, "two"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2025-01-23 15:55:11 +00:00
|
|
|
names, err := GetSources(db)
|
2025-01-16 21:46:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
expected := []string{"one", "three"}
|
|
|
|
for i := 0; i < len(expected); i += 1 {
|
|
|
|
if !slices.Contains(names, expected[i]) {
|
|
|
|
t.Fatalf("missing %s, have: %v", expected[i], names)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func AssertItemIs(t *testing.T, item Item, expected string) {
|
|
|
|
actual := fmt.Sprintf(
|
|
|
|
"%s/%s/%t/%s/%s/%s/%s/%d",
|
2025-01-17 15:05:57 +00:00
|
|
|
item.Source,
|
|
|
|
item.Id,
|
|
|
|
item.Active,
|
|
|
|
item.Title,
|
|
|
|
item.Author,
|
|
|
|
item.Body,
|
|
|
|
item.Link,
|
|
|
|
item.Time,
|
2025-01-16 21:46:30 +00:00
|
|
|
)
|
|
|
|
if actual != expected {
|
|
|
|
t.Fatalf("expected %s, got %s", expected, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAddItem(t *testing.T) {
|
|
|
|
db := EphemeralDb(t)
|
|
|
|
if err := AddSource(db, "test"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := AddItem(db, "test", "one", "", "", "", "", 0); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := AddItem(db, "test", "two", "title", "author", "body", "link", 123456); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2025-01-17 15:05:57 +00:00
|
|
|
items, err := GetActiveItemsForSource(db, "test")
|
2025-01-16 21:46:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(items) != 2 {
|
|
|
|
t.Fatal("should get two items")
|
|
|
|
}
|
|
|
|
AssertItemIs(t, items[0], "test/one/true/////0")
|
|
|
|
AssertItemIs(t, items[1], "test/two/true/title/author/body/link/123456")
|
|
|
|
|
2025-01-17 06:02:03 +00:00
|
|
|
if _, err = DeactivateItem(db, "test", "one"); err != nil {
|
2025-01-16 21:46:30 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2025-01-17 15:05:57 +00:00
|
|
|
items, err = GetActiveItemsForSource(db, "test")
|
2025-01-16 21:46:30 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(items) != 1 {
|
|
|
|
t.Fatal("should get one item")
|
|
|
|
}
|
2025-01-23 19:38:17 +00:00
|
|
|
|
|
|
|
items, err = GetAllItemsForSource(db, "test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(items) != 2 {
|
|
|
|
t.Fatal("should get two items")
|
|
|
|
}
|
|
|
|
|
|
|
|
deleted, err := DeleteItem(db, "test", "one")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if deleted != 1 {
|
|
|
|
t.Fatal("expected one deletion")
|
|
|
|
}
|
|
|
|
|
|
|
|
deleted, err = DeleteItem(db, "test", "one")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if deleted != 0 {
|
|
|
|
t.Fatal("expected no deletion")
|
|
|
|
}
|
|
|
|
|
|
|
|
items, err = GetAllItemsForSource(db, "test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(items) != 1 {
|
|
|
|
t.Fatal("should get one item")
|
|
|
|
}
|
2025-01-16 21:46:30 +00:00
|
|
|
}
|
2025-01-23 20:26:21 +00:00
|
|
|
|
|
|
|
func TestUpdateSourceAddAndDelete(t *testing.T) {
|
|
|
|
db := EphemeralDb(t)
|
|
|
|
if err := AddSource(db, "test"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
a := Item{Source: "test", Id: "a"}
|
|
|
|
add, del, err := UpdateWithFetchedItems(db, "test", []Item{a})
|
|
|
|
if add != 1 || del != 0 || err != nil {
|
|
|
|
t.Fatalf("update failed: add %d, del %d, err %v", add, del, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
add, del, err = UpdateWithFetchedItems(db, "test", []Item{a})
|
|
|
|
if add != 0 || del != 0 || err != nil {
|
|
|
|
t.Fatalf("update failed: add %d, del %d, err %v", add, del, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b := Item{Source: "test", Id: "b"}
|
|
|
|
add, del, err = UpdateWithFetchedItems(db, "test", []Item{a, b})
|
|
|
|
if add != 1 || del != 0 || err != nil {
|
|
|
|
t.Fatalf("update failed: add %d, del %d, err %v", add, del, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = DeactivateItem(db, "test", "a"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
add, del, err = UpdateWithFetchedItems(db, "test", []Item{a, b})
|
|
|
|
if add != 0 || del != 0 || err != nil {
|
|
|
|
t.Fatalf("update failed: add %d, del %d, err %v", add, del, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
add, del, err = UpdateWithFetchedItems(db, "test", []Item{b})
|
|
|
|
if add != 0 || del != 1 || err != nil {
|
|
|
|
t.Fatalf("update failed: add %d, del %d, err %v", add, del, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
add, del, err = UpdateWithFetchedItems(db, "test", []Item{b})
|
|
|
|
if add != 0 || del != 0 || err != nil {
|
|
|
|
t.Fatalf("update failed: add %d, del %d, err %v", add, del, err)
|
|
|
|
}
|
|
|
|
}
|
2025-01-24 23:41:52 +00:00
|
|
|
|
|
|
|
func TestOnCreateAction(t *testing.T) {
|
|
|
|
db := EphemeralDb(t)
|
|
|
|
if err := AddSource(db, "test"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := AddAction(db, "test", "on_create", []string{"true"}); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
execute := func(argv []string) []Item {
|
|
|
|
items, err := Execute("test", argv, nil, "", time.Minute)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("unexpected error executing test fetch")
|
|
|
|
}
|
|
|
|
if len(items) != 1 {
|
|
|
|
t.Fatalf("expected only one item, got %d", len(items))
|
|
|
|
}
|
|
|
|
return items
|
|
|
|
}
|
|
|
|
|
|
|
|
onCreate := func(argv []string) {
|
|
|
|
if err := UpdateAction(db, "test", "on_create", argv); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getItem := func(id string) Item {
|
|
|
|
item, err := GetItem(db, "test", id)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
return item
|
|
|
|
}
|
|
|
|
|
|
|
|
// Noop on_create works
|
|
|
|
onCreate([]string{"tee"})
|
|
|
|
items := execute([]string{"jq", "-cn", `{id: "one"}`})
|
|
|
|
add, _, err := UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with noop oncreate")
|
|
|
|
}
|
|
|
|
updated := getItem("one")
|
|
|
|
updated.Created = 0 // zero out for comparison with pre-insert item
|
|
|
|
if updated != items[0] {
|
|
|
|
t.Fatalf("expected no change: %#v != %#v", updated, items[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
// on_create can change a field
|
|
|
|
onCreate([]string{"jq", "-c", `.title = "Goodbye, World"`})
|
|
|
|
items = execute([]string{"jq", "-cn", `{id: "two", title: "Hello, World"}`})
|
|
|
|
if items[0].Title != "Hello, World" {
|
|
|
|
t.Fatal("unexpected title")
|
|
|
|
}
|
|
|
|
add, _, err = UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with alter oncreate")
|
|
|
|
}
|
|
|
|
if getItem("two").Title != "Goodbye, World" {
|
|
|
|
t.Fatal("title not updated")
|
|
|
|
}
|
|
|
|
|
|
|
|
// on_create can add a field
|
|
|
|
onCreate([]string{"jq", "-c", `.link = "gopher://go.dev"`})
|
|
|
|
items = execute([]string{"jq", "-cn", `{id: "three"}`})
|
|
|
|
if items[0].Link != "" {
|
|
|
|
t.Fatal("unexpected link")
|
|
|
|
}
|
|
|
|
add, _, err = UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with augment oncreate")
|
|
|
|
}
|
|
|
|
if getItem("three").Link != "gopher://go.dev" {
|
|
|
|
t.Fatal("link not added")
|
|
|
|
}
|
|
|
|
|
|
|
|
// on_create can't delete a field using a zero value
|
|
|
|
// due to zero values preserving prior field values
|
|
|
|
onCreate([]string{"jq", "-c", `del(.link)`})
|
|
|
|
items = execute([]string{"jq", "-cn", `{id: "four", link: "gopher://go.dev"}`})
|
|
|
|
if items[0].Link != "gopher://go.dev" {
|
|
|
|
t.Fatal("missing link")
|
|
|
|
}
|
|
|
|
add, _, err = UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with attempted deletion oncreate")
|
|
|
|
}
|
|
|
|
if getItem("four").Link != "gopher://go.dev" {
|
|
|
|
t.Fatal("link unexpectedly removed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// item is created if on_create fails
|
|
|
|
onCreate([]string{"false"})
|
|
|
|
items = execute([]string{"jq", "-cn", `{id: "five"}`})
|
|
|
|
add, _, err = UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with failing oncreate")
|
|
|
|
}
|
|
|
|
if getItem("five").Id != "five" {
|
|
|
|
t.Fatal("item not created")
|
|
|
|
}
|
|
|
|
|
|
|
|
// item isn't updated if on_create has valid output but a bad exit code
|
|
|
|
onCreate([]string{"sh", "-c", `jq -cn '{id: "six", title: "after"}'; exit 1`})
|
|
|
|
items = execute([]string{"jq", "-cn", `{id: "six", title: "before"}`})
|
|
|
|
if items[0].Title != "before" {
|
|
|
|
t.Fatal("unexpected title")
|
|
|
|
}
|
|
|
|
add, _, err = UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with bad exit code oncreate")
|
|
|
|
}
|
|
|
|
if getItem("six").Title != "before" {
|
|
|
|
t.Fatal("update applied after oncreate failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
// on_create can't change id, active, or created
|
|
|
|
onCreate([]string{"jq", "-c", `.id = "seven"; .active = false; .created = 123456`})
|
|
|
|
items = execute([]string{"jq", "-cn", `{id: "seven"}`})
|
|
|
|
add, _, err = UpdateWithFetchedItems(db, "test", items)
|
|
|
|
if add != 1 || err != nil {
|
|
|
|
t.Fatal("failed update with invalid field changes oncreate")
|
|
|
|
}
|
|
|
|
updated = getItem("seven")
|
|
|
|
if updated.Id != "seven" || !updated.Active || updated.Created == 123456 {
|
|
|
|
t.Fatal("unexpected changes to id, active, or created fields")
|
|
|
|
}
|
|
|
|
}
|