1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package engine
5
6import (
7	"strings"
8	"testing"
9	"time"
10
11	"github.com/keybase/client/go/libkb"
12	keybase1 "github.com/keybase/client/go/protocol/keybase1"
13	"github.com/keybase/clockwork"
14)
15
16type flakeyRooterAPI struct {
17	orig     libkb.ExternalAPI
18	flakeOut bool
19	hardFail bool
20	G        *libkb.GlobalContext
21}
22
23func (e *flakeyRooterAPI) GetText(m libkb.MetaContext, arg libkb.APIArg) (*libkb.ExternalTextRes, error) {
24	e.G.Log.Debug("| flakeyRooterAPI.GetText, hard = %v, flake = %v", e.hardFail, e.flakeOut)
25	return e.orig.GetText(m, arg)
26}
27
28func (e *flakeyRooterAPI) Get(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalAPIRes, err error) {
29	e.G.Log.Debug("| flakeyRooterAPI.Get, hard = %v, flake = %v", e.hardFail, e.flakeOut)
30	// Show an error if we're in flakey mode
31	if strings.Contains(arg.Endpoint, "rooter") {
32		if e.hardFail {
33			return &libkb.ExternalAPIRes{HTTPStatus: 404}, &libkb.APIError{Msg: "NotFound", Code: 404}
34		}
35		if e.flakeOut {
36			return &libkb.ExternalAPIRes{HTTPStatus: 429}, &libkb.APIError{Msg: "Ratelimited", Code: 429}
37		}
38	}
39
40	return e.orig.Get(m, arg)
41}
42
43func (e *flakeyRooterAPI) GetHTML(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalHTMLRes, err error) {
44	e.G.Log.Debug("| flakeyRooterAPI.GetHTML, hard = %v, flake = %v", e.hardFail, e.flakeOut)
45	return e.orig.GetHTML(m, arg)
46}
47
48func (e *flakeyRooterAPI) Post(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalAPIRes, err error) {
49	return e.orig.Post(m, arg)
50}
51
52func (e *flakeyRooterAPI) PostHTML(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalHTMLRes, err error) {
53	return e.orig.PostHTML(m, arg)
54}
55
56func TestSoftSnooze(t *testing.T) {
57	tc := SetupEngineTest(t, "track")
58	defer tc.Cleanup()
59	sigVersion := libkb.GetDefaultSigVersion(tc.G)
60
61	fakeClock := clockwork.NewFakeClockAt(time.Now())
62	tc.G.SetClock(fakeClock)
63	fu := CreateAndSignupFakeUser(tc, "track")
64
65	flakeyAPI := flakeyRooterAPI{orig: tc.G.XAPI, flakeOut: false, G: tc.G}
66	tc.G.XAPI = &flakeyAPI
67
68	idUI := &FakeIdentifyUI{}
69	username := "t_tracy"
70	arg := &keybase1.Identify2Arg{
71		UserAssertion:    username,
72		NeedProofSet:     true,
73		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CLI,
74	}
75	uis := libkb.UIs{
76		LogUI:      tc.G.UI.GetLogUI(),
77		IdentifyUI: idUI,
78		SecretUI:   fu.NewSecretUI(),
79	}
80	// Identify tracy; all proofs should work
81	eng := NewResolveThenIdentify2(tc.G, arg)
82	m := NewMetaContextForTest(tc).WithUIs(uis)
83	if err := RunEngine2(m, eng); err != nil {
84		t.Fatal(err)
85	}
86	sv := keybase1.SigVersion(sigVersion)
87	targ := TrackTokenArg{
88		Token:   idUI.Token,
89		Options: keybase1.TrackOptions{BypassConfirm: true, SigVersion: &sv},
90	}
91
92	// Track tracy
93	teng := NewTrackToken(tc.G, &targ)
94	if err := RunEngine2(m, teng); err != nil {
95		t.Fatal(err)
96	}
97
98	defer func() { _ = runUntrack(tc, fu, username, sigVersion) }()
99
100	// Now make her Rooter proof flakey / fail with a 429
101	flakeyAPI.flakeOut = true
102	idUI = &FakeIdentifyUI{}
103	m = m.WithIdentifyUI(idUI)
104
105	// Advance so that our previous cached success is out of
106	// cache on its own, but still can override a 429-like soft failure.
107	fakeClock.Advance(tc.G.Env.GetProofCacheMediumDur() + time.Minute)
108
109	eng = NewResolveThenIdentify2(tc.G, arg)
110	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
111	// Should not get an error
112	if err := RunEngine2(m, eng); err != nil {
113		t.Fatal(err)
114	}
115	result, found := idUI.ProofResults["rooter"]
116	if !found {
117		t.Fatal("Failed to find a rooter proof")
118	}
119	if pe := libkb.ImportProofError(result.SnoozedResult); pe == nil {
120		t.Fatal("expected a snoozed error result")
121	}
122
123	// Now time out the success that allowed us to circumvent
124	// the soft failure.
125	fakeClock.Advance(tc.G.Env.GetProofCacheLongDur())
126	eng = NewResolveThenIdentify2(tc.G, arg)
127	eng.testArgs = &Identify2WithUIDTestArgs{noCache: true}
128	idUI = &FakeIdentifyUI{}
129	m = m.WithIdentifyUI(idUI)
130	if err := RunEngine2(m, eng); err == nil {
131		t.Fatal("Expected a failure in our proof")
132	}
133
134	result, found = idUI.ProofResults["rooter"]
135	if !found {
136		t.Fatal("Failed to find a rooter proof")
137	}
138	if pe := libkb.ImportProofError(result.ProofResult); pe == nil {
139		t.Fatal("expected a Rooter error result")
140	}
141	if !idUI.BrokenTracking {
142		t.Fatal("expected broken tracking!")
143	}
144
145	assertTracking(tc, username)
146}
147