Skip to content
quarry Artifact [ac541eb52d]
Login

Artifact [ac541eb52d]

Artifact ac541eb52d60a57b4069fcbad916c875faf098343c00445f68f6cd466bd13ffe:


package api

import (
	"context"
	"encoding/json"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/ops"
)

// A test push says what the push services answered, from notify's own
// record — the texts here are the ones zeitforge recorded for an
// account with one Chrome and two iPhone subscriptions — and never
// carries a device's endpoint.
func TestAPushOutcomeSaysWhoRefusedAndWhy(t *testing.T) {
	cases := []struct {
		name    string
		in      PushOutcome
		outcome string
		got, of int
		why     string
	}{
		{"not yet", PushOutcome{}, "unknown", 0, 0, ""},
		{"all", PushOutcome{Found: true, Status: "sent"}, "delivered", 2, 2, ""},
		{"apple signature", PushOutcome{Found: true, Status: "sent",
			Error: `delivered 1/2: 1 permanent failure(s) (webpush: status 403: {"reason":"BadJwtToken"})`}, "partial", 1, 2, "403 BadJwtToken"},
		{"timeout and signature", PushOutcome{Found: true, Status: "sent",
			Error: `delivered 1/3: 2 permanent failure(s) (webpush: status 403: {"reason":"BadJwtToken"}; webpush: send: Post "https://web.push.apple.com/QF_secretEndpoint": context deadline exceeded (Client.Timeout exceeded while awaiting headers))`},
			"partial", 1, 3, "403 BadJwtToken; timed out"},
		{"none", PushOutcome{Found: true, Status: "error",
			Error: `webpush: status 403: {"reason":"BadJwtToken"}; webpush: status 403: {"reason":"BadJwtToken"}`}, "failed", 0, 0, "2× 403 BadJwtToken"},
	}
	for _, c := range cases {
		s := summarizePush(c.in, 2)
		why := strings.Join(s.Why, "; ")
		if s.Outcome != c.outcome || s.Delivered != c.got || s.Of != c.of || why != c.why {
			t.Errorf("FAIL %s: %+v (why %q)", c.name, s, why)
		}
		if strings.Contains(why, "secretEndpoint") {
			t.Errorf("FAIL %s: a device endpoint leaked into the summary", c.name)
		}
	}
}

// The op hands notify's per-device outcomes on beside the overall result.
func TestThePushTestReportsEachDevice(t *testing.T) {
	np := &outcomeNotify{out: PushOutcome{Found: true, Status: "sent", Devices: []PushDeviceOutcome{
		{Host: "fcm.googleapis.com", Outcome: "delivered"},
		{Host: "web.push.apple.com", Outcome: "gone", Status: 410},
	}}}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "t", Notify: np}); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(ops.WithUser(context.Background(), "dan"), "me.push.test", mePushTestIn{})
	if err != nil {
		t.Fatal(err)
	}
	b, _ := json.Marshal(got)
	var wire struct {
		Outcome string `json:"outcome"`
		Results []struct {
			Host    string `json:"host"`
			Outcome string `json:"outcome"`
			Status  int    `json:"status"`
		} `json:"results"`
	}
	if err := json.Unmarshal(b, &wire); err != nil {
		t.Fatal(err)
	}
	if wire.Outcome == "" || len(wire.Results) != 2 ||
		wire.Results[1].Host != "web.push.apple.com" || wire.Results[1].Outcome != "gone" || wire.Results[1].Status != 410 {
		t.Errorf("FAIL: wire %s", b)
	}
}

type outcomeNotify struct {
	fakeNotify
	out PushOutcome
}

func (o *outcomeNotify) PushOutcome(context.Context, string, string) (PushOutcome, error) {
	return o.out, nil
}