func TestFinder(t *testing.T) { Convey("Finder", t, func() { test.LoadScenario("paths") conn := test.OpenDatabase(test.StellarCoreDatabaseUrl()) defer conn.Close() finder := &Finder{ Ctx: test.Context(), SqlQuery: db.SqlQuery{conn}, } native := makeAsset(xdr.AssetTypeAssetTypeNative, "", "") usd := makeAsset( xdr.AssetTypeAssetTypeCreditAlphanum4, "USD", "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN") eur := makeAsset( xdr.AssetTypeAssetTypeCreditAlphanum4, "EUR", "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN") Convey("Find", func() { query := paths.Query{ DestinationAddress: "GAEDTJ4PPEFVW5XV2S7LUXBEHNQMX5Q2GM562RJGOQG7GVCE5H3HIB4V", DestinationAsset: eur, DestinationAmount: xdr.Int64(200000000), SourceAssets: []xdr.Asset{usd}, } paths, err := finder.Find(query) So(err, ShouldBeNil) So(len(paths), ShouldEqual, 4) query.DestinationAmount = xdr.Int64(200000001) paths, err = finder.Find(query) So(err, ShouldBeNil) So(len(paths), ShouldEqual, 2) query.DestinationAmount = xdr.Int64(500000001) paths, err = finder.Find(query) So(err, ShouldBeNil) So(len(paths), ShouldEqual, 0) }) Convey("regression: paths that involve native currencies can be found", func() { query := paths.Query{ DestinationAddress: "GDSBCQO34HWPGUGQSP3QBFEXVTSR2PW46UIGTHVWGWJGQKH3AFNHXHXN", DestinationAsset: native, DestinationAmount: xdr.Int64(1), SourceAssets: []xdr.Asset{usd, native}, } paths, err := finder.Find(query) So(err, ShouldBeNil) So(len(paths), ShouldEqual, 2) }) }) }
func TestQueue(t *testing.T) { ctx := test.Context() _ = ctx Convey("Queue", t, func() { queue := NewQueue() Convey("Push adds the provided channel on to the priority queue", func() { So(queue.Size(), ShouldEqual, 0) queue.Push(2) So(queue.Size(), ShouldEqual, 1) _, s := queue.head() So(s, ShouldEqual, 2) queue.Push(1) So(queue.Size(), ShouldEqual, 2) _, s = queue.head() So(s, ShouldEqual, 1) }) Convey("Update removes sequences that are submittable or in the past", func() { results := []<-chan error{ queue.Push(1), queue.Push(2), queue.Push(3), queue.Push(4), } queue.Update(2) // the update above signifies that 2 is the accounts current sequence, // meaning that 3 is submittable, and so only 4 should still be queued So(queue.Size(), ShouldEqual, 1) _, s := queue.head() So(s, ShouldEqual, 4) queue.Update(4) So(queue.Size(), ShouldEqual, 0) So(<-results[0], ShouldEqual, ErrBadSequence) So(<-results[1], ShouldEqual, ErrBadSequence) So(<-results[2], ShouldEqual, nil) So(<-results[3], ShouldEqual, ErrBadSequence) }) Convey("Update clears the queue if the head has not been released within the time limit", func() { queue.timeout = 1 * time.Millisecond result := queue.Push(2) <-time.After(10 * time.Millisecond) queue.Update(0) So(queue.Size(), ShouldEqual, 0) So(<-result, ShouldEqual, ErrBadSequence) }) }) }
func TestMain(m *testing.M) { ctx = test.Context() core = OpenStellarCoreTestDatabase() history = OpenTestDatabase() defer core.Close() defer history.Close() os.Exit(m.Run()) }
func TestStreaming(t *testing.T) { ctx := test.Context() ctx, cancel := context.WithCancel(ctx) db := test.OpenDatabase(test.DatabaseUrl()) Convey("LedgerClosePump", t, func() { Convey("can cancel", func() { pump := NewLedgerClosePump(ctx, db) cancel() _, more := <-pump So(more, ShouldBeFalse) }) }) }
func TestLedgerState(t *testing.T) { test.LoadScenario("base") horizon := OpenTestDatabase() defer horizon.Close() core := OpenStellarCoreTestDatabase() defer core.Close() Convey("db.UpdateLedgerState", t, func() { So(horizonLedgerGauge.Value(), ShouldEqual, 0) So(stellarCoreLedgerGauge.Value(), ShouldEqual, 0) UpdateLedgerState(test.Context(), SqlQuery{horizon}, SqlQuery{core}) So(horizonLedgerGauge.Value(), ShouldEqual, 3) So(stellarCoreLedgerGauge.Value(), ShouldEqual, 3) }) }
func ShouldBeProblem(a interface{}, options ...interface{}) string { body := a.(*bytes.Buffer) expected := options[0].(problem.P) problem.Inflate(test.Context(), &expected) var actual problem.P err := json.Unmarshal(body.Bytes(), &actual) if err != nil { return fmt.Sprintf("Could not unmarshal json into problem struct:\n%s\n", body.String()) } if expected.Type != "" && actual.Type != expected.Type { return fmt.Sprintf("Mismatched problem type: %s expected, got %s", expected.Type, actual.Type) } if expected.Status != 0 && actual.Status != expected.Status { return fmt.Sprintf("Mismatched problem status: %s expected, got %s", expected.Status, actual.Status) } return "" }
func TestApp(t *testing.T) { Convey("NewApp establishes the app in its context", t, func() { app, err := NewApp(NewTestConfig()) So(err, ShouldBeNil) defer app.Close() found, ok := AppFromContext(app.ctx) So(ok, ShouldBeTrue) So(found, ShouldEqual, app) }) Convey("NewApp panics if the provided config's SentryDSN is invalid", t, func() { config := NewTestConfig() config.SentryDSN = "Not a url" So(func() { app, _ := NewApp(config) app.Close() }, ShouldPanic) }) Convey("CORS support", t, func() { app := NewTestApp() defer app.Close() rh := NewRequestHelper(app) w := rh.Get("/", test.RequestHelperNoop) So(w.Code, ShouldEqual, 200) So(w.HeaderMap.Get("Access-Control-Allow-Origin"), ShouldEqual, "") w = rh.Get("/", func(r *http.Request) { r.Header.Set("Origin", "somewhere.com") }) So(w.Code, ShouldEqual, 200) So(w.HeaderMap.Get("Access-Control-Allow-Origin"), ShouldEqual, "somewhere.com") }) Convey("Trailing slash causes redirect", t, func() { test.LoadScenario("base") app := NewTestApp() defer app.Close() rh := NewRequestHelper(app) w := rh.Get("/accounts", test.RequestHelperNoop) So(w.Code, ShouldEqual, 200) w = rh.Get("/accounts/", test.RequestHelperNoop) So(w.Code, ShouldEqual, 200) }) Convey("app.UpdateMetrics", t, func() { test.LoadScenario("base") app := NewTestApp() defer app.Close() So(app.horizonLedgerGauge.Value(), ShouldEqual, 0) So(app.stellarCoreLedgerGauge.Value(), ShouldEqual, 0) app.UpdateMetrics(test.Context()) So(app.horizonLedgerGauge.Value(), ShouldEqual, 3) So(app.stellarCoreLedgerGauge.Value(), ShouldEqual, 3) }) }
func TestDefaultSubmitter(t *testing.T) { ctx := test.Context() Convey("submitter (The default Submitter implementation)", t, func() { Convey("submits to the configured stellar-core instance correctly", func() { server := test.NewStaticMockServer(`{ "status": "PENDING", "error": null }`) defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL) sr := s.Submit(ctx, "hello") So(sr.Err, ShouldBeNil) So(sr.Duration, ShouldBeGreaterThan, 0) So(server.LastRequest.URL.Query().Get("blob"), ShouldEqual, "hello") }) Convey("succeeds when the stellar-core responds with DUPLICATE status", func() { server := test.NewStaticMockServer(`{ "status": "DUPLICATE", "error": null }`) defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL) sr := s.Submit(ctx, "hello") So(sr.Err, ShouldBeNil) }) Convey("errors when the stellar-core url is empty", func() { s := NewDefaultSubmitter(http.DefaultClient, "") sr := s.Submit(ctx, "hello") So(sr.Err, ShouldNotBeNil) }) Convey("errors when the stellar-core url is not parseable", func() { s := NewDefaultSubmitter(http.DefaultClient, "http://Not a url") sr := s.Submit(ctx, "hello") So(sr.Err, ShouldNotBeNil) }) Convey("errors when the stellar-core url is not reachable", func() { s := NewDefaultSubmitter(http.DefaultClient, "http://127.0.0.1:65535") sr := s.Submit(ctx, "hello") So(sr.Err, ShouldNotBeNil) }) Convey("errors when the stellar-core returns an unparseable response", func() { server := test.NewStaticMockServer(`{`) defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL) sr := s.Submit(ctx, "hello") So(sr.Err, ShouldNotBeNil) }) Convey("errors when the stellar-core returns an exception response", func() { server := test.NewStaticMockServer(`{"exception": "Invalid XDR"}`) defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL) sr := s.Submit(ctx, "hello") So(sr.Err, ShouldNotBeNil) So(sr.Err.Error(), ShouldContainSubstring, "Invalid XDR") }) Convey("errors when the stellar-core returns an unrecognized status", func() { server := test.NewStaticMockServer(`{"status": "NOTREAL"}`) defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL) sr := s.Submit(ctx, "hello") So(sr.Err, ShouldNotBeNil) So(sr.Err.Error(), ShouldContainSubstring, "NOTREAL") }) Convey("errors when the stellar-core returns an error response", func() { server := test.NewStaticMockServer(`{"status": "ERROR", "error": "1234"}`) defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL) sr := s.Submit(ctx, "hello") So(sr.Err, ShouldHaveSameTypeAs, &FailedTransactionError{}) ferr := sr.Err.(*FailedTransactionError) So(ferr.ResultXDR, ShouldEqual, "1234") }) }) }
func TestDefaultSubmissionList(t *testing.T) { ctx := test.Context() Convey("submissionList (The default OpenSubmissionList implementation)", t, func() { list := NewDefaultSubmissionList() realList := list.(*submissionList) hashes := []string{ "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000001", } listeners := []chan Result{ make(chan Result, 1), make(chan Result, 1), } Convey("Add()", func() { Convey("adds an entry to the submission list when a new hash is used", func() { list.Add(ctx, hashes[0], listeners[0]) sub := realList.submissions[hashes[0]] So(sub.Hash, ShouldEqual, hashes[0]) So(sub.SubmittedAt, ShouldHappenWithin, 1*time.Second, time.Now()) // drop the send side of the channel by casting to listener var l Listener = listeners[0] So(sub.Listeners[0], ShouldEqual, l) }) Convey("adds an listener to an existing entry when a hash is used with a new listener", func() { list.Add(ctx, hashes[0], listeners[0]) sub := realList.submissions[hashes[0]] st := sub.SubmittedAt <-time.After(20 * time.Millisecond) list.Add(ctx, hashes[0], listeners[1]) // increases the size of the listener So(len(sub.Listeners), ShouldEqual, 2) // doesn't update the submitted at time So(st == sub.SubmittedAt, ShouldEqual, true) }) Convey("panics when the listener is not buffered", func() { So(func() { list.Add(ctx, hashes[0], make(Listener)) }, ShouldPanic) }) Convey("errors when the provided hash is not 64-bytes", func() { err := list.Add(ctx, "123", listeners[0]) So(err, ShouldNotBeNil) }) }) Convey("Finish()", func() { list.Add(ctx, hashes[0], listeners[0]) list.Add(ctx, hashes[0], listeners[1]) r := Result{ Hash: hashes[0], } list.Finish(ctx, r) Convey("writes to every listener", func() { r1, ok1 := <-listeners[0] So(r1, ShouldResemble, r) So(ok1, ShouldBeTrue) r2, ok2 := <-listeners[1] So(r2, ShouldResemble, r) So(ok2, ShouldBeTrue) }) Convey("removes the entry", func() { _, ok := realList.submissions[hashes[0]] So(ok, ShouldBeFalse) }) Convey("closes every listener", func() { _, _ = <-listeners[0] _, more := <-listeners[0] So(more, ShouldBeFalse) _, _ = <-listeners[1] _, more = <-listeners[1] So(more, ShouldBeFalse) }) Convey("works when the noone is waiting for the result", func() { err := list.Finish(ctx, r) So(err, ShouldBeNil) }) }) Convey("Clean()", func() { list.Add(ctx, hashes[0], listeners[0]) <-time.After(200 * time.Millisecond) list.Add(ctx, hashes[1], listeners[1]) left, err := list.Clean(ctx, 200*time.Millisecond) So(err, ShouldBeNil) So(left, ShouldEqual, 1) Convey("removes submissions older than the maxAge provided", func() { _, ok := realList.submissions[hashes[0]] So(ok, ShouldBeFalse) }) Convey("leaves submissions that are younger than the maxAge provided", func() { _, ok := realList.submissions[hashes[1]] So(ok, ShouldBeTrue) }) Convey("closes any cleaned listeners", func() { select { case _, stillOpen := <-listeners[0]: So(stillOpen, ShouldBeFalse) default: panic("cleaned listener is still open") } }) }) Convey("Pending() works as expected", func() { So(len(list.Pending(ctx)), ShouldEqual, 0) list.Add(ctx, hashes[0], listeners[0]) So(len(list.Pending(ctx)), ShouldEqual, 1) list.Add(ctx, hashes[1], listeners[1]) So(len(list.Pending(ctx)), ShouldEqual, 2) }) }) }
func TestHelpers(t *testing.T) { Convey("Action Helpers", t, func() { r, _ := http.NewRequest("GET", "/?limit=2&cursor=hello", nil) action := &Base{ Ctx: test.Context(), GojiCtx: web.C{ URLParams: map[string]string{ "blank": "", "zero": "0", "two": "2", "32min": fmt.Sprint(math.MinInt32), "32max": fmt.Sprint(math.MaxInt32), "64min": fmt.Sprint(math.MinInt64), "64max": fmt.Sprint(math.MaxInt64), "native_type": "native", "4_type": "credit_alphanum4", "12_type": "credit_alphanum12", }, Env: map[interface{}]interface{}{}, }, R: r, } Convey("GetInt32", func() { result := action.GetInt32("blank") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt32("zero") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt32("two") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 2) result = action.GetInt32("32max") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MaxInt32) result = action.GetInt32("32min") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MinInt32) result = action.GetInt32("limit") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 2) result = action.GetInt32("64max") So(action.Err, ShouldNotBeNil) result = action.GetInt32("64min") So(action.Err, ShouldNotBeNil) }) Convey("GetInt64", func() { result := action.GetInt64("blank") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt64("zero") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt64("two") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 2) result = action.GetInt64("64max") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MaxInt64) result = action.GetInt64("64min") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MinInt64) }) Convey("GetPagingParams", func() { cursor, order, limit := action.GetPagingParams() So(cursor, ShouldEqual, "hello") So(limit, ShouldEqual, 2) So(order, ShouldEqual, "") }) Convey("GetAssetType", func() { t := action.GetAssetType("native_type") So(t, ShouldEqual, xdr.AssetTypeAssetTypeNative) t = action.GetAssetType("4_type") So(t, ShouldEqual, xdr.AssetTypeAssetTypeCreditAlphanum4) t = action.GetAssetType("12_type") So(t, ShouldEqual, xdr.AssetTypeAssetTypeCreditAlphanum12) So(action.Err, ShouldBeNil) action.GetAssetType("cursor") So(action.Err, ShouldNotBeNil) }) Convey("Last-Event-ID overrides cursor", func() { action.R.Header.Set("Last-Event-ID", "from_header") cursor, _, _ := action.GetPagingParams() So(cursor, ShouldEqual, "from_header") }) Convey("Form values override query values", func() { So(action.GetString("cursor"), ShouldEqual, "hello") action.R.Form = url.Values{ "cursor": {"goodbye"}, } So(action.GetString("cursor"), ShouldEqual, "goodbye") }) Convey("regression: GetPagQuery does not overwrite err", func() { r, _ := http.NewRequest("GET", "/?limit=foo", nil) action.R = r _, _, _ = action.GetPagingParams() So(action.Err, ShouldNotBeNil) _ = action.GetPageQuery() So(action.Err, ShouldNotBeNil) }) Convey("Path() return the action's http path", func() { r, _ := http.NewRequest("GET", "/foo-bar/blah?limit=foo", nil) action.R = r So(action.Path(), ShouldEqual, "/foo-bar/blah") }) }) }
func TestTxsub(t *testing.T) { Convey("txsub.System", t, func() { ctx := test.Context() submitter := &MockSubmitter{} results := &MockResultProvider{} system := &System{ Pending: NewDefaultSubmissionList(), Submitter: submitter, Results: results, NetworkPassphrase: build.TestNetwork.Passphrase, } noResults := Result{Err: ErrNoResults} successTx := Result{ Hash: "c492d87c4642815dfb3c7dcce01af4effd162b031064098a0d786b6e0a00fd74", LedgerSequence: 2, EnvelopeXDR: "AAAAAGL8HQvQkbK2HA3WVjRrKmjX00fG8sLI7m0ERwJW/AX3AAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAABVvwF9wAAAEAKZ7IPj/46PuWU6ZOtyMosctNAkXRNX9WCAI5RnfRk+AyxDLoDZP/9l3NvsxQtWj9juQOuoBlFLnWu8intgxQA", ResultXDR: "xJLYfEZCgV37PH3M4Br07/0WKwMQZAmKDXhrbgoA/XQAAAAAAAAACgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA==", } badSeq := SubmissionResult{ Err: &FailedTransactionError{"AAAAAAAAAAD////7AAAAAA=="}, } Convey("Submit", func() { Convey("returns the result provided by the ResultProvider", func() { results.Results = []Result{successTx} r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldBeNil) So(r.Hash, ShouldEqual, successTx.Hash) So(submitter.WasSubmittedTo, ShouldBeFalse) }) Convey("returns the error from submission if no result is found by hash and the submitter returns an error", func() { submitter.R.Err = errors.New("busted for some reason") r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldNotBeNil) So(submitter.WasSubmittedTo, ShouldBeTrue) So(system.Metrics.SuccessfulSubmissionsMeter.Count(), ShouldEqual, 0) So(system.Metrics.FailedSubmissionsMeter.Count(), ShouldEqual, 1) So(system.Metrics.SubmissionTimer.Count(), ShouldEqual, 1) }) Convey("if the error is bad_seq and the result at the transaction's sequence number is for the same hash, return result", func() { submitter.R = badSeq results.Results = []Result{noResults, successTx} r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldBeNil) So(r.Hash, ShouldEqual, successTx.Hash) So(submitter.WasSubmittedTo, ShouldBeTrue) }) Convey("if error is bad_seq and no result is found, return error", func() { submitter.R = badSeq r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldNotBeNil) So(submitter.WasSubmittedTo, ShouldBeTrue) }) Convey("if no result found and no error submitting, add to open transaction list", func() { _ = system.Submit(ctx, successTx.EnvelopeXDR) pending := system.Pending.Pending(ctx) So(len(pending), ShouldEqual, 1) So(pending[0], ShouldEqual, successTx.Hash) So(system.Metrics.SuccessfulSubmissionsMeter.Count(), ShouldEqual, 1) So(system.Metrics.FailedSubmissionsMeter.Count(), ShouldEqual, 0) So(system.Metrics.SubmissionTimer.Count(), ShouldEqual, 1) }) }) Convey("Tick", func() { Convey("no-ops if there are no open submissions", func() { system.Tick(ctx) }) Convey("finishes any available transactions", func() { l := make(chan Result, 1) system.Pending.Add(ctx, successTx.Hash, l) system.Tick(ctx) So(len(l), ShouldEqual, 0) So(len(system.Pending.Pending(ctx)), ShouldEqual, 1) results.Results = []Result{successTx} system.Tick(ctx) So(len(l), ShouldEqual, 1) So(len(system.Pending.Pending(ctx)), ShouldEqual, 0) }) Convey("removes old submissions that have timed out", func() { l := make(chan Result, 1) system.SubmissionTimeout = 100 * time.Millisecond system.Pending.Add(ctx, successTx.Hash, l) <-time.After(101 * time.Millisecond) system.Tick(ctx) So(len(system.Pending.Pending(ctx)), ShouldEqual, 0) select { case _, stillOpen := <-l: So(stillOpen, ShouldBeFalse) default: panic("could not read from listener") } }) }) }) }
func TestHelpers(t *testing.T) { Convey("Action Helpers", t, func() { r, _ := http.NewRequest("GET", "/?limit=2&cursor=hello", nil) action := &Base{ Ctx: test.Context(), GojiCtx: web.C{ URLParams: map[string]string{ "blank": "", "zero": "0", "two": "2", "32min": fmt.Sprint(math.MinInt32), "32max": fmt.Sprint(math.MaxInt32), "64min": fmt.Sprint(math.MinInt64), "64max": fmt.Sprint(math.MaxInt64), "native_asset_type": "native", "4_asset_type": "credit_alphanum4", "4_asset_code": "USD", "4_asset_issuer": "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H", "12_asset_type": "credit_alphanum12", "12_asset_code": "USD", "12_asset_issuer": "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H", }, Env: map[interface{}]interface{}{}, }, R: r, } Convey("GetInt32", func() { result := action.GetInt32("blank") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt32("zero") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt32("two") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 2) result = action.GetInt32("32max") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MaxInt32) result = action.GetInt32("32min") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MinInt32) result = action.GetInt32("limit") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 2) result = action.GetInt32("64max") So(action.Err, ShouldHaveSameTypeAs, &problem.P{}) p := action.Err.(*problem.P) So(p.Type, ShouldEqual, "bad_request") So(p.Extras["invalid_field"], ShouldEqual, "64max") action.Err = nil result = action.GetInt32("64min") So(action.Err, ShouldHaveSameTypeAs, &problem.P{}) p = action.Err.(*problem.P) So(p.Type, ShouldEqual, "bad_request") So(p.Extras["invalid_field"], ShouldEqual, "64min") action.Err = nil }) Convey("GetInt64", func() { result := action.GetInt64("blank") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt64("zero") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 0) result = action.GetInt64("two") So(action.Err, ShouldBeNil) So(result, ShouldEqual, 2) result = action.GetInt64("64max") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MaxInt64) result = action.GetInt64("64min") So(action.Err, ShouldBeNil) So(result, ShouldEqual, math.MinInt64) }) Convey("GetPagingParams", func() { cursor, order, limit := action.GetPagingParams() So(cursor, ShouldEqual, "hello") So(limit, ShouldEqual, 2) So(order, ShouldEqual, "") }) Convey("GetAccountID", func() { _ = action.GetAccountID("4_asset_issuer") So(action.Err, ShouldBeNil) }) Convey("GetAsset", func() { ts := action.GetAsset("native_") So(action.Err, ShouldBeNil) So(ts.Type, ShouldEqual, xdr.AssetTypeAssetTypeNative) ts = action.GetAsset("4_") So(action.Err, ShouldBeNil) So(ts.Type, ShouldEqual, xdr.AssetTypeAssetTypeCreditAlphanum4) ts = action.GetAsset("12_") So(action.Err, ShouldBeNil) So(ts.Type, ShouldEqual, xdr.AssetTypeAssetTypeCreditAlphanum12) So(action.Err, ShouldBeNil) action.GetAsset("cursor") So(action.Err, ShouldNotBeNil) }) Convey("GetAssetType", func() { t := action.GetAssetType("native_asset_type") So(t, ShouldEqual, xdr.AssetTypeAssetTypeNative) t = action.GetAssetType("4_asset_type") So(t, ShouldEqual, xdr.AssetTypeAssetTypeCreditAlphanum4) t = action.GetAssetType("12_asset_type") So(t, ShouldEqual, xdr.AssetTypeAssetTypeCreditAlphanum12) So(action.Err, ShouldBeNil) action.GetAssetType("cursor") So(action.Err, ShouldNotBeNil) }) Convey("Last-Event-ID overrides cursor", func() { action.R.Header.Set("Last-Event-ID", "from_header") cursor, _, _ := action.GetPagingParams() So(cursor, ShouldEqual, "from_header") }) Convey("Form values override query values", func() { So(action.GetString("cursor"), ShouldEqual, "hello") action.R.Form = url.Values{ "cursor": {"goodbye"}, } So(action.GetString("cursor"), ShouldEqual, "goodbye") }) Convey("regression: GetPagQuery does not overwrite err", func() { r, _ := http.NewRequest("GET", "/?limit=foo", nil) action.R = r _, _, _ = action.GetPagingParams() So(action.Err, ShouldNotBeNil) _ = action.GetPageQuery() So(action.Err, ShouldNotBeNil) }) Convey("Path() return the action's http path", func() { r, _ := http.NewRequest("GET", "/foo-bar/blah?limit=foo", nil) action.R = r So(action.Path(), ShouldEqual, "/foo-bar/blah") }) }) }
func TestTxsub(t *testing.T) { Convey("txsub.System", t, func() { ctx := test.Context() submitter := &MockSubmitter{} results := &MockResultProvider{} sequences := &MockSequenceProvider{} system := &System{ Pending: NewDefaultSubmissionList(), Submitter: submitter, Results: results, Sequences: sequences, SubmissionQueue: sequence.NewManager(), NetworkPassphrase: build.TestNetwork.Passphrase, } noResults := Result{Err: ErrNoResults} successTx := Result{ Hash: "2374e99349b9ef7dba9a5db3339b78fda8f34777b1af33ba468ad5c0df946d4d", LedgerSequence: 2, EnvelopeXDR: "AAAAAGL8HQvQkbK2HA3WVjRrKmjX00fG8sLI7m0ERwJW/AX3AAAAZAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAABVvwF9wAAAECDzqvkQBQoNAJifPRXDoLhvtycT3lFPCQ51gkdsFHaBNWw05S/VhW0Xgkr0CBPE4NaFV2Kmcs3ZwLmib4TRrML", ResultXDR: "I3Tpk0m57326ml2zM5t4/ajzR3exrzO6RorVwN+UbU0AAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA==", } badSeq := SubmissionResult{ Err: ErrBadSequence, } sequences.Results = map[string]uint64{ "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H": 0, } Convey("Submit", func() { Convey("returns the result provided by the ResultProvider", func() { results.Results = []Result{successTx} r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldBeNil) So(r.Hash, ShouldEqual, successTx.Hash) So(submitter.WasSubmittedTo, ShouldBeFalse) }) Convey("returns the error from submission if no result is found by hash and the submitter returns an error", func() { submitter.R.Err = errors.New("busted for some reason") r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldNotBeNil) So(submitter.WasSubmittedTo, ShouldBeTrue) So(system.Metrics.SuccessfulSubmissionsMeter.Count(), ShouldEqual, 0) So(system.Metrics.FailedSubmissionsMeter.Count(), ShouldEqual, 1) So(system.Metrics.SubmissionTimer.Count(), ShouldEqual, 1) }) Convey("if the error is bad_seq and the result at the transaction's sequence number is for the same hash, return result", func() { submitter.R = badSeq results.Results = []Result{noResults, successTx} r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldBeNil) So(r.Hash, ShouldEqual, successTx.Hash) So(submitter.WasSubmittedTo, ShouldBeTrue) }) Convey("if error is bad_seq and no result is found, return error", func() { submitter.R = badSeq r := <-system.Submit(ctx, successTx.EnvelopeXDR) So(r.Err, ShouldNotBeNil) So(submitter.WasSubmittedTo, ShouldBeTrue) }) Convey("if no result found and no error submitting, add to open transaction list", func() { _ = system.Submit(ctx, successTx.EnvelopeXDR) pending := system.Pending.Pending(ctx) So(len(pending), ShouldEqual, 1) So(pending[0], ShouldEqual, successTx.Hash) So(system.Metrics.SuccessfulSubmissionsMeter.Count(), ShouldEqual, 1) So(system.Metrics.FailedSubmissionsMeter.Count(), ShouldEqual, 0) So(system.Metrics.SubmissionTimer.Count(), ShouldEqual, 1) }) }) Convey("Tick", func() { Convey("no-ops if there are no open submissions", func() { system.Tick(ctx) }) Convey("finishes any available transactions", func() { l := make(chan Result, 1) system.Pending.Add(ctx, successTx.Hash, l) system.Tick(ctx) So(len(l), ShouldEqual, 0) So(len(system.Pending.Pending(ctx)), ShouldEqual, 1) results.Results = []Result{successTx} system.Tick(ctx) So(len(l), ShouldEqual, 1) So(len(system.Pending.Pending(ctx)), ShouldEqual, 0) }) Convey("removes old submissions that have timed out", func() { l := make(chan Result, 1) system.SubmissionTimeout = 100 * time.Millisecond system.Pending.Add(ctx, successTx.Hash, l) <-time.After(101 * time.Millisecond) system.Tick(ctx) So(len(system.Pending.Pending(ctx)), ShouldEqual, 0) So(len(l), ShouldEqual, 1) <-l select { case _, stillOpen := <-l: So(stillOpen, ShouldBeFalse) default: panic("could not read from listener") } }) }) }) }