Skip to content
quarry Artifact [4349321b6b]
Login

Artifact [4349321b6b]

Artifact 4349321b6bb9ccc45b8c9ae8f62e9b7f60045dba4d15a053c4780c4557d84f26:


package oltp

import (
	"path/filepath"
	"testing"
)

func pushDB(t *testing.T) *DB {
	t.Helper()
	db, err := Open(filepath.Join(t.TempDir(), "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	return db
}

// A device is remembered by the HASH of its endpoint, never the
// endpoint itself: a push endpoint is a bearer capability to notify
// somebody's phone, and quarry has no reason to hold one — notify does.
func TestPushDeviceRoundTrip(t *testing.T) {
	db := pushDB(t)
	const ep = "https://push.example/abcdef"
	if err := db.AddPushDevice("dan", ep, "Firefox on Linux"); err != nil {
		t.Fatal(err)
	}
	devs, err := db.PushDevices("dan")
	if err != nil {
		t.Fatal(err)
	}
	if len(devs) != 1 {
		t.Fatalf("devices = %+v", devs)
	}
	if devs[0].UA != "Firefox on Linux" || devs[0].At == "" {
		t.Fatalf("device = %+v", devs[0])
	}
	if devs[0].EndpointHash == ep || len(devs[0].EndpointHash) != 64 {
		t.Fatalf("endpoint must be stored as a sha-256 hex hash, got %q", devs[0].EndpointHash)
	}
	if devs[0].EndpointHash != PushEndpointHash(ep) {
		t.Fatalf("hash is not derivable from the endpoint")
	}
}

// Re-registering the same browser refreshes the row rather than adding
// a second one: one endpoint is one device however often the page
// re-subscribes.
func TestPushDeviceIsIdempotentPerEndpoint(t *testing.T) {
	db := pushDB(t)
	for i := 0; i < 3; i++ {
		if err := db.AddPushDevice("dan", "https://push.example/a", "UA"); err != nil {
			t.Fatal(err)
		}
	}
	devs, _ := db.PushDevices("dan")
	if len(devs) != 1 {
		t.Fatalf("devices = %+v", devs)
	}
}

func TestPushDeviceDelete(t *testing.T) {
	db := pushDB(t)
	db.AddPushDevice("dan", "https://push.example/a", "one")
	db.AddPushDevice("dan", "https://push.example/b", "two")
	db.AddPushDevice("alice", "https://push.example/c", "hers")
	if err := db.DeletePushDevice("dan", "https://push.example/a"); err != nil {
		t.Fatal(err)
	}
	devs, _ := db.PushDevices("dan")
	if len(devs) != 1 || devs[0].UA != "two" {
		t.Fatalf("devices = %+v", devs)
	}
	// Another account's device is untouched by a namesake delete.
	if devs, _ := db.PushDevices("alice"); len(devs) != 1 {
		t.Fatalf("alice's devices = %+v", devs)
	}
}

// An unsubscribe notify would not confirm must leave the device row in
// place, and must leave quarry able to ask again — which needs the
// endpoint, not just its hash. Forgetting the row instead is how a
// browser keeps receiving pushes with nothing left in the interface to
// stop it.
func TestPushDevicePendingDeleteSurvivesAndRetries(t *testing.T) {
	db := pushDB(t)
	const endpoint = "https://push.example.test/abc"
	if err := db.AddPushDevice("dan", endpoint, "Firefox"); err != nil {
		t.Fatal(err)
	}
	if pending, err := db.PendingPushDeletes("dan"); err != nil || len(pending) != 0 {
		t.Fatalf("a fresh device is not pending: %+v %v", pending, err)
	}
	if err := db.MarkPushDevicePendingDelete("dan", endpoint); err != nil {
		t.Fatal(err)
	}
	devs, err := db.PushDevices("dan")
	if err != nil {
		t.Fatal(err)
	}
	if len(devs) != 1 || !devs[0].PendingDelete {
		t.Fatalf("the row must stay, marked: %+v", devs)
	}
	pending, err := db.PendingPushDeletes("dan")
	if err != nil {
		t.Fatal(err)
	}
	if len(pending) != 1 || pending[0] != endpoint {
		t.Fatalf("the retry needs the endpoint back: %+v", pending)
	}
	// Confirmed at last: the row goes, and with it the endpoint quarry
	// was only holding to finish the job.
	if err := db.DeletePushDevice("dan", endpoint); err != nil {
		t.Fatal(err)
	}
	if devs, _ := db.PushDevices("dan"); len(devs) != 0 {
		t.Fatalf("devices = %+v", devs)
	}
	if pending, _ := db.PendingPushDeletes("dan"); len(pending) != 0 {
		t.Fatalf("pending = %+v", pending)
	}
}

// Re-subscribing the same endpoint clears a stale pending mark: the
// device is wanted again, so nothing should be retrying its removal.
func TestAddPushDeviceClearsAPendingDelete(t *testing.T) {
	db := pushDB(t)
	const endpoint = "https://push.example.test/abc"
	if err := db.AddPushDevice("dan", endpoint, "Firefox"); err != nil {
		t.Fatal(err)
	}
	if err := db.MarkPushDevicePendingDelete("dan", endpoint); err != nil {
		t.Fatal(err)
	}
	if err := db.AddPushDevice("dan", endpoint, "Firefox"); err != nil {
		t.Fatal(err)
	}
	if pending, _ := db.PendingPushDeletes("dan"); len(pending) != 0 {
		t.Fatalf("re-subscribing must clear the mark: %+v", pending)
	}
}