<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package browspr

import (
	"fmt"
	"testing"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/vdl"

	_ "v.io/x/ref/runtime/factories/generic"
	"v.io/x/ref/services/wspr/internal/principal"
	"v.io/x/ref/test"
)

const topLevelName = "mock-blesser"

type mockBlesserService struct {
	p     security.Principal
	count int
}

func newMockBlesserService(p security.Principal) *mockBlesserService {
	return &mockBlesserService{
		p:     p,
		count: 0,
	}
}

func (m *mockBlesserService) BlessUsingAccessToken(c *context.T, accessToken string, co ...rpc.CallOpt) (security.Blessings, string, error) {
	m.count++
	name := fmt.Sprintf("%s%s%d", topLevelName, security.ChainSeparator, m.count)
	blessing, err := m.p.BlessSelf(name)
	if err != nil {
		return blessing, "", err
	}
	return blessing, name, nil
}

func setup(t *testing.T) (*Browspr, func()) {
	ctx, shutdown := test.V23Init()

	spec := v23.GetListenSpec(ctx)
	spec.Proxy = "/mock/proxy"
	mockPostMessage := func(_ int32, _, _ string) {}
	browspr := NewBrowspr(ctx, mockPostMessage, &spec, "/mock:1234/identd", nil, principal.NewInMemorySerializer())
	principal := v23.GetPrincipal(browspr.ctx)
	browspr.accountManager.SetMockBlesser(newMockBlesserService(principal))

	return browspr, func() {
		browspr.Shutdown()
		shutdown()
	}
}

func TestHandleCreateAccount(t *testing.T) {
	browspr, teardown := setup(t)
	defer teardown()

	// Verify that HandleAuthGetAccountsRpc returns empty.
	nilValue := vdl.ValueOf(GetAccountsMessage{})
	a, err := browspr.HandleAuthGetAccountsRpc(nilValue)
	if err != nil {
		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
	}
	if a.Len() > 0 {
		t.Fatalf("Expected accounts to be empty array but got %v", a)
	}

	// Add one account.
	message1 := vdl.ValueOf(CreateAccountMessage{
		Token: "mock-access-token-1",
	})
	account1, err := browspr.HandleAuthCreateAccountRpc(message1)
	if err != nil {
		t.Fatalf("browspr.HandleAuthCreateAccountRpc(%v) failed: %v", message1, err)
	}

	// Verify that principalManager has the new account
	if b, err := browspr.principalManager.BlessingsForAccount(account1.RawString()); err != nil || b.IsZero() {
		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account1, b, err)
	}

	// Verify that HandleAuthGetAccountsRpc returns the new account.
	gotAccounts1, err := browspr.HandleAuthGetAccountsRpc(nilValue)
	if err != nil {
		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
	}
	if want := vdl.ValueOf([]string{account1.RawString()}); !vdl.EqualValue(want, gotAccounts1) {
		t.Fatalf("Expected account to be %v but got empty but got %v", want, gotAccounts1)
	}

	// Add another account
	message2 := vdl.ValueOf(CreateAccountMessage{
		Token: "mock-access-token-2",
	})
	account2, err := browspr.HandleAuthCreateAccountRpc(message2)
	if err != nil {
		t.Fatalf("browspr.HandleAuthCreateAccountsRpc(%v) failed: %v", message2, err)
	}

	// Verify that HandleAuthGetAccountsRpc returns the new account.
	gotAccounts2, err := browspr.HandleAuthGetAccountsRpc(nilValue)
	if err != nil {
		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
	}
	if want := vdl.ValueOf([]string{account1.RawString(), account2.RawString()}); !vdl.EqualValue(want, gotAccounts2) {
		t.Fatalf("Expected account to be %v but got empty but got %v", want, gotAccounts2)
	}

	// Verify that principalManager has both accounts
	if b, err := browspr.principalManager.BlessingsForAccount(account1.RawString()); err != nil || b.IsZero() {
		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account1, b, err)
	}
	if b, err := browspr.principalManager.BlessingsForAccount(account2.RawString()); err != nil || b.IsZero() {
		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account2, b, err)
	}
}

func TestHandleAssocAccount(t *testing.T) {
	browspr, teardown := setup(t)
	defer teardown()

	// First create an account.
	account := "mock-account"
	principal := v23.GetPrincipal(browspr.ctx)
	blessing, err := principal.BlessSelf(account)
	if err != nil {
		t.Fatalf("browspr.rt.Principal.BlessSelf(%v) failed: %v", account, err)
	}
	if err := browspr.principalManager.AddAccount(account, blessing); err != nil {
		t.Fatalf("browspr.principalManager.AddAccount(%v, %v) failed; %v", account, blessing, err)
	}

	origin := "https://my.webapp.com:443"

	// Verify that HandleAuthOriginHasAccountRpc returns false
	hasAccountMessage := vdl.ValueOf(OriginHasAccountMessage{
		Origin: origin,
	})
	hasAccount, err := browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
	if err != nil {
		t.Fatal(err)
	}
	if hasAccount.Bool() {
		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be false but was true", hasAccountMessage)
	}

	assocAccountMessage := vdl.ValueOf(AssociateAccountMessage{
		Account: account,
		Origin:  origin,
	})

	if _, err := browspr.HandleAuthAssociateAccountRpc(assocAccountMessage); err != nil {
		t.Fatalf("browspr.HandleAuthAssociateAccountRpc(%v) failed: %v", assocAccountMessage, err)
	}

	// Verify that HandleAuthOriginHasAccountRpc returns true
	hasAccount, err = browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
	if err != nil {
		t.Fatal(err)
	}
	if !hasAccount.Bool() {
		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be true but was false", hasAccountMessage)
	}

	// Verify that principalManager has the correct principal for the origin
	got, err := browspr.principalManager.Principal(origin)
	if err != nil {
		t.Fatalf("browspr.principalManager.Principal(%v) failed: %v", origin, err)
	}

	if got == nil {
		t.Fatalf("Expected browspr.principalManager.Principal(%v) to return a valid principal, but got %v", origin, got)
	}
}

func TestHandleAssocAccountWithMissingAccount(t *testing.T) {
	browspr, teardown := setup(t)
	defer teardown()

	account := "mock-account"
	origin := "https://my.webapp.com:443"
	message := vdl.ValueOf(AssociateAccountMessage{
		Account: account,
		Origin:  origin,
	})

	if _, err := browspr.HandleAuthAssociateAccountRpc(message); err == nil {
		t.Fatalf("browspr.HandleAuthAssociateAccountRpc(%v) should have failed but did not.", message)
	}

	// Verify that principalManager creates no principal for the origin
	got, err := browspr.principalManager.Principal(origin)
	if err == nil {
		t.Fatalf("Expected browspr.principalManager.Principal(%v) to fail, but got: %v", origin, got)
	}

	if got != nil {
		t.Fatalf("Expected browspr.principalManager.Principal(%v) not to return a principal, but got %v", origin, got)
	}

	// Verify that HandleAuthOriginHasAccountRpc returns false
	hasAccountMessage := vdl.ValueOf(OriginHasAccountMessage{
		Origin: origin,
	})
	hasAccount, err := browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
	if err != nil {
		t.Fatal(err)
	}
	if hasAccount.Bool() {
		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be false but was true", hasAccountMessage)
	}
}
<commit_msg>ref/services/wspr: Fix flaky test.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package browspr

import (
	"fmt"
	"reflect"
	"sort"
	"testing"

	"v.io/v23"
	"v.io/v23/context"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/vdl"

	_ "v.io/x/ref/runtime/factories/generic"
	"v.io/x/ref/services/wspr/internal/principal"
	"v.io/x/ref/test"
)

const topLevelName = "mock-blesser"

type mockBlesserService struct {
	p     security.Principal
	count int
}

func newMockBlesserService(p security.Principal) *mockBlesserService {
	return &mockBlesserService{
		p:     p,
		count: 0,
	}
}

func (m *mockBlesserService) BlessUsingAccessToken(c *context.T, accessToken string, co ...rpc.CallOpt) (security.Blessings, string, error) {
	m.count++
	name := fmt.Sprintf("%s%s%d", topLevelName, security.ChainSeparator, m.count)
	blessing, err := m.p.BlessSelf(name)
	if err != nil {
		return blessing, "", err
	}
	return blessing, name, nil
}

func setup(t *testing.T) (*Browspr, func()) {
	ctx, shutdown := test.V23Init()

	spec := v23.GetListenSpec(ctx)
	spec.Proxy = "/mock/proxy"
	mockPostMessage := func(_ int32, _, _ string) {}
	browspr := NewBrowspr(ctx, mockPostMessage, &spec, "/mock:1234/identd", nil, principal.NewInMemorySerializer())
	principal := v23.GetPrincipal(browspr.ctx)
	browspr.accountManager.SetMockBlesser(newMockBlesserService(principal))

	return browspr, func() {
		browspr.Shutdown()
		shutdown()
	}
}

func TestHandleCreateAccount(t *testing.T) {
	browspr, teardown := setup(t)
	defer teardown()

	// Verify that HandleAuthGetAccountsRpc returns empty.
	nilValue := vdl.ValueOf(GetAccountsMessage{})
	a, err := browspr.HandleAuthGetAccountsRpc(nilValue)
	if err != nil {
		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
	}
	if a.Len() > 0 {
		t.Fatalf("Expected accounts to be empty array but got %v", a)
	}

	// Add one account.
	message1 := vdl.ValueOf(CreateAccountMessage{
		Token: "mock-access-token-1",
	})
	account1, err := browspr.HandleAuthCreateAccountRpc(message1)
	if err != nil {
		t.Fatalf("browspr.HandleAuthCreateAccountRpc(%v) failed: %v", message1, err)
	}

	// Verify that principalManager has the new account
	if b, err := browspr.principalManager.BlessingsForAccount(account1.RawString()); err != nil || b.IsZero() {
		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account1, b, err)
	}

	// Verify that HandleAuthGetAccountsRpc returns the new account.
	gotAccounts1, err := browspr.HandleAuthGetAccountsRpc(nilValue)
	if err != nil {
		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
	}
	if want := vdl.ValueOf([]string{account1.RawString()}); !vdl.EqualValue(want, gotAccounts1) {
		t.Fatalf("Expected account to be %v but got empty but got %v", want, gotAccounts1)
	}

	// Add another account
	message2 := vdl.ValueOf(CreateAccountMessage{
		Token: "mock-access-token-2",
	})
	account2, err := browspr.HandleAuthCreateAccountRpc(message2)
	if err != nil {
		t.Fatalf("browspr.HandleAuthCreateAccountsRpc(%v) failed: %v", message2, err)
	}

	// Verify that HandleAuthGetAccountsRpc returns the new account.
	gotAccounts2, err := browspr.HandleAuthGetAccountsRpc(nilValue)
	if err != nil {
		t.Fatalf("browspr.HandleAuthGetAccountsRpc(%v) failed: %v", nilValue, err)
	}
	var got []string
	if err := vdl.Convert(&got, gotAccounts2); err != nil {
		t.Fatalf("vdl.Convert failed: %v", err)
	}
	sort.Strings(got)
	if want := []string{account1.RawString(), account2.RawString()}; !reflect.DeepEqual(want, got) {
		t.Fatalf("Expected account to be %v but got %v", want, got)
	}

	// Verify that principalManager has both accounts
	if b, err := browspr.principalManager.BlessingsForAccount(account1.RawString()); err != nil || b.IsZero() {
		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account1, b, err)
	}
	if b, err := browspr.principalManager.BlessingsForAccount(account2.RawString()); err != nil || b.IsZero() {
		t.Fatalf("Failed to get Blessings for account %v: got %v, %v", account2, b, err)
	}
}

func TestHandleAssocAccount(t *testing.T) {
	browspr, teardown := setup(t)
	defer teardown()

	// First create an account.
	account := "mock-account"
	principal := v23.GetPrincipal(browspr.ctx)
	blessing, err := principal.BlessSelf(account)
	if err != nil {
		t.Fatalf("browspr.rt.Principal.BlessSelf(%v) failed: %v", account, err)
	}
	if err := browspr.principalManager.AddAccount(account, blessing); err != nil {
		t.Fatalf("browspr.principalManager.AddAccount(%v, %v) failed; %v", account, blessing, err)
	}

	origin := "https://my.webapp.com:443"

	// Verify that HandleAuthOriginHasAccountRpc returns false
	hasAccountMessage := vdl.ValueOf(OriginHasAccountMessage{
		Origin: origin,
	})
	hasAccount, err := browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
	if err != nil {
		t.Fatal(err)
	}
	if hasAccount.Bool() {
		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be false but was true", hasAccountMessage)
	}

	assocAccountMessage := vdl.ValueOf(AssociateAccountMessage{
		Account: account,
		Origin:  origin,
	})

	if _, err := browspr.HandleAuthAssociateAccountRpc(assocAccountMessage); err != nil {
		t.Fatalf("browspr.HandleAuthAssociateAccountRpc(%v) failed: %v", assocAccountMessage, err)
	}

	// Verify that HandleAuthOriginHasAccountRpc returns true
	hasAccount, err = browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
	if err != nil {
		t.Fatal(err)
	}
	if !hasAccount.Bool() {
		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be true but was false", hasAccountMessage)
	}

	// Verify that principalManager has the correct principal for the origin
	got, err := browspr.principalManager.Principal(origin)
	if err != nil {
		t.Fatalf("browspr.principalManager.Principal(%v) failed: %v", origin, err)
	}

	if got == nil {
		t.Fatalf("Expected browspr.principalManager.Principal(%v) to return a valid principal, but got %v", origin, got)
	}
}

func TestHandleAssocAccountWithMissingAccount(t *testing.T) {
	browspr, teardown := setup(t)
	defer teardown()

	account := "mock-account"
	origin := "https://my.webapp.com:443"
	message := vdl.ValueOf(AssociateAccountMessage{
		Account: account,
		Origin:  origin,
	})

	if _, err := browspr.HandleAuthAssociateAccountRpc(message); err == nil {
		t.Fatalf("browspr.HandleAuthAssociateAccountRpc(%v) should have failed but did not.", message)
	}

	// Verify that principalManager creates no principal for the origin
	got, err := browspr.principalManager.Principal(origin)
	if err == nil {
		t.Fatalf("Expected browspr.principalManager.Principal(%v) to fail, but got: %v", origin, got)
	}

	if got != nil {
		t.Fatalf("Expected browspr.principalManager.Principal(%v) not to return a principal, but got %v", origin, got)
	}

	// Verify that HandleAuthOriginHasAccountRpc returns false
	hasAccountMessage := vdl.ValueOf(OriginHasAccountMessage{
		Origin: origin,
	})
	hasAccount, err := browspr.HandleAuthOriginHasAccountRpc(hasAccountMessage)
	if err != nil {
		t.Fatal(err)
	}
	if hasAccount.Bool() {
		t.Fatalf("Expected browspr.HandleAuthOriginHasAccountRpc(%v) to be false but was true", hasAccountMessage)
	}
}
<|endoftext|>


<commit_before><commit_msg>Check pawns can promotion to be the argument or not<commit_after><|endoftext|>


<commit_before>package http2

import (
	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/spec"
	"golang.org/x/net/http2"
)

func StreamStates() *spec.TestGroup {
	tg := NewTestGroup("5.1", "Stream States")

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a DATA frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			conn.WriteData(1, true, []byte("test"))

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			conn.WriteRSTStream(1, http2.ErrCodeCancel)

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a WINDOW_UPDATE frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			conn.WriteWindowUpdate(1, 100)

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a CONTINUATION frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			blockFragment := conn.EncodeHeaders(headers)
			conn.WriteContinuation(1, true, blockFragment)

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// half-closed (remote):
	// If an endpoint receives additional frames, other than
	// WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
	// this state, it MUST respond with a stream error (Section 5.4.2)
	// of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "half closed (remote): Sends a DATA frame",
		Requirement: "The endpoint MUST respond with a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// half-closed (remote):
	// If an endpoint receives additional frames, other than
	// WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
	// this state, it MUST respond with a stream error (Section 5.4.2)
	// of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "half closed (remote): Sends a HEADERS frame",
		Requirement: "The endpoint MUST respond with a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)

			hp1 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp1)

			hp2 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp2)

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// half-closed (remote):
	// If an endpoint receives additional frames, other than
	// WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
	// this state, it MUST respond with a stream error (Section 5.4.2)
	// of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "half closed (remote): Sends a CONTINUATION frame",
		Requirement: "The endpoint MUST respond with a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			blockFragment := conn.EncodeHeaders(headers)

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: blockFragment,
			}

			conn.WriteHeaders(hp)
			conn.WriteContinuation(streamID, true, blockFragment)

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed, http2.ErrCodeProtocol)
		},
	})

	// closed:
	// An endpoint that receives any frame other than PRIORITY after
	// receiving a RST_STREAM MUST treat that as a stream error
	// (Section 5.4.2) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a DATA frame after sending RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			conn.WriteRSTStream(streamID, http2.ErrCodeCancel)

			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frame other than PRIORITY after
	// receiving a RST_STREAM MUST treat that as a stream error
	// (Section 5.4.2) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a HEADERS frame after sending RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp1 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp1)

			conn.WriteRSTStream(streamID, http2.ErrCodeCancel)

			hp2 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp2)

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frame other than PRIORITY after
	// receiving a RST_STREAM MUST treat that as a stream error
	// (Section 5.4.2) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a CONTINUATION frame after sending RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			conn.WriteRSTStream(streamID, http2.ErrCodeCancel)

			dummyHeaders := spec.DummyHeaders(c, 1)
			conn.WriteContinuation(streamID, true, conn.EncodeHeaders(dummyHeaders))

			codes := []http2.ErrCode{
				http2.ErrCodeStreamClosed,
				http2.ErrCodeProtocol,
			}
			return spec.VerifyStreamError(conn, codes...)
		},
	})

	// closed:
	// An endpoint that receives any frames after receiving a frame
	// with the END_STREAM flag set MUST treat that as a connection
	// error (Section 6.4.1) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a DATA frame",
		Requirement: "The endpoint MUST treat this as a connection error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			err = spec.VerifyStreamClose(conn)
			if err != nil {
				return err
			}

			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyConnectionError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frames after receiving a frame
	// with the END_STREAM flag set MUST treat that as a connection
	// error (Section 6.4.1) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a HEADERS frame",
		Requirement: "The endpoint MUST treat this as a connection error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			err = spec.VerifyStreamClose(conn)
			if err != nil {
				return err
			}

			conn.WriteHeaders(hp)

			return spec.VerifyConnectionError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frames after receiving a frame
	// with the END_STREAM flag set MUST treat that as a connection
	// error (Section 6.4.1) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a CONTINUATION frame",
		Requirement: "The endpoint MUST treat this as a connection error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			err = spec.VerifyStreamClose(conn)
			if err != nil {
				return err
			}

			dummyHeaders := spec.DummyHeaders(c, 1)
			conn.WriteContinuation(streamID, true, conn.EncodeHeaders(dummyHeaders))

			codes := []http2.ErrCode{
				http2.ErrCodeStreamClosed,
				http2.ErrCodeProtocol,
			}
			return spec.VerifyConnectionError(conn, codes...)
		},
	})

	tg.AddTestGroup(StreamIdentifiers())
	tg.AddTestGroup(StreamConcurrency())

	return tg
}
<commit_msg>Verify stream error instead of connection error in http2/5.1/11<commit_after>package http2

import (
	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/spec"
	"golang.org/x/net/http2"
)

func StreamStates() *spec.TestGroup {
	tg := NewTestGroup("5.1", "Stream States")

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a DATA frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			conn.WriteData(1, true, []byte("test"))

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			conn.WriteRSTStream(1, http2.ErrCodeCancel)

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a WINDOW_UPDATE frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			conn.WriteWindowUpdate(1, 100)

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// idle:
	// Receiving any frame other than HEADERS or PRIORITY on a stream
	// in this state MUST be treated as a connection error
	// (Section 5.4.1) of type PROTOCOL_ERROR.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "idle: Sends a CONTINUATION frame",
		Requirement: "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			blockFragment := conn.EncodeHeaders(headers)
			conn.WriteContinuation(1, true, blockFragment)

			return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol)
		},
	})

	// half-closed (remote):
	// If an endpoint receives additional frames, other than
	// WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
	// this state, it MUST respond with a stream error (Section 5.4.2)
	// of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "half closed (remote): Sends a DATA frame",
		Requirement: "The endpoint MUST respond with a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// half-closed (remote):
	// If an endpoint receives additional frames, other than
	// WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
	// this state, it MUST respond with a stream error (Section 5.4.2)
	// of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "half closed (remote): Sends a HEADERS frame",
		Requirement: "The endpoint MUST respond with a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)

			hp1 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp1)

			hp2 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp2)

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// half-closed (remote):
	// If an endpoint receives additional frames, other than
	// WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
	// this state, it MUST respond with a stream error (Section 5.4.2)
	// of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "half closed (remote): Sends a CONTINUATION frame",
		Requirement: "The endpoint MUST respond with a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			blockFragment := conn.EncodeHeaders(headers)

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: blockFragment,
			}

			conn.WriteHeaders(hp)
			conn.WriteContinuation(streamID, true, blockFragment)

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed, http2.ErrCodeProtocol)
		},
	})

	// closed:
	// An endpoint that receives any frame other than PRIORITY after
	// receiving a RST_STREAM MUST treat that as a stream error
	// (Section 5.4.2) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a DATA frame after sending RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			conn.WriteRSTStream(streamID, http2.ErrCodeCancel)

			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frame other than PRIORITY after
	// receiving a RST_STREAM MUST treat that as a stream error
	// (Section 5.4.2) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a HEADERS frame after sending RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp1 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp1)

			conn.WriteRSTStream(streamID, http2.ErrCodeCancel)

			hp2 := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp2)

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frame other than PRIORITY after
	// receiving a RST_STREAM MUST treat that as a stream error
	// (Section 5.4.2) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a CONTINUATION frame after sending RST_STREAM frame",
		Requirement: "The endpoint MUST treat this as a stream error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			conn.WriteRSTStream(streamID, http2.ErrCodeCancel)

			dummyHeaders := spec.DummyHeaders(c, 1)
			conn.WriteContinuation(streamID, true, conn.EncodeHeaders(dummyHeaders))

			codes := []http2.ErrCode{
				http2.ErrCodeStreamClosed,
				http2.ErrCodeProtocol,
			}
			return spec.VerifyStreamError(conn, codes...)
		},
	})

	// closed:
	// An endpoint that receives any frames after receiving a frame
	// with the END_STREAM flag set MUST treat that as a connection
	// error (Section 6.4.1) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a DATA frame",
		Requirement: "The endpoint MUST treat this as a connection error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			err = spec.VerifyStreamClose(conn)
			if err != nil {
				return err
			}

			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyStreamError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frames after receiving a frame
	// with the END_STREAM flag set MUST treat that as a connection
	// error (Section 6.4.1) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a HEADERS frame",
		Requirement: "The endpoint MUST treat this as a connection error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			err = spec.VerifyStreamClose(conn)
			if err != nil {
				return err
			}

			conn.WriteHeaders(hp)

			return spec.VerifyConnectionError(conn, http2.ErrCodeStreamClosed)
		},
	})

	// closed:
	// An endpoint that receives any frames after receiving a frame
	// with the END_STREAM flag set MUST treat that as a connection
	// error (Section 6.4.1) of type STREAM_CLOSED.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "closed: Sends a CONTINUATION frame",
		Requirement: "The endpoint MUST treat this as a connection error of type STREAM_CLOSED.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     true,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}
			conn.WriteHeaders(hp)

			err = spec.VerifyStreamClose(conn)
			if err != nil {
				return err
			}

			dummyHeaders := spec.DummyHeaders(c, 1)
			conn.WriteContinuation(streamID, true, conn.EncodeHeaders(dummyHeaders))

			codes := []http2.ErrCode{
				http2.ErrCodeStreamClosed,
				http2.ErrCodeProtocol,
			}
			return spec.VerifyConnectionError(conn, codes...)
		},
	})

	tg.AddTestGroup(StreamIdentifiers())
	tg.AddTestGroup(StreamConcurrency())

	return tg
}
<|endoftext|>


<commit_before><commit_msg>Deleted master<commit_after><|endoftext|>


<commit_before>package torrent

import "time"

// Config for Session.
type Config struct {
	// Database file to save resume data.
	Database string
	// DataDir is where files are downloaded.
	DataDir string
	// New torrents will be listened at selected port in this range.
	PortBegin, PortEnd uint16
	// At start, client will set max open files limit to this number. (like "ulimit -n" command)
	MaxOpenFiles uint64
	// Enable peer exchange protocol.
	PEXEnabled bool
	// Bitfield is saved to disk for fast resume without hash checking.
	// There is an interval to keep IO lower.
	BitfieldWriteInterval time.Duration
	// Stats are written at interval to reduce write operations.
	StatsWriteInterval time.Duration
	// Peer id is prefixed with this string. See BEP 20. Remaining bytes of peer id will be randomized.
	PeerIDPrefix string
	// Client version that is sent in BEP 10 handshake message.
	ExtensionHandshakeClientVersion string
	// URL to the blocklist file in CIDR format.
	BlocklistURL string
	// When to refresh blocklist
	BlocklistUpdateInterval time.Duration

	// Enable RPC server
	RPCEnabled bool
	// Host to listen for RPC server
	RPCHost string
	// Listen port for RPC server
	RPCPort int
	// Time to wait for ongoing requests before shutting down RPC HTTP server.
	RPCShutdownTimeout time.Duration

	// Enable DHT node.
	DHTEnabled bool
	// DHT node will listen on this IP.
	DHTAddress string
	// DHT node will listen on this UDP port.
	DHTPort uint16
	// DHT announce interval
	DHTAnnounceInterval time.Duration
	// Minimum announce interval when announcing to DHT.
	DHTMinAnnounceInterval time.Duration

	// Number of peer addresses to request in announce request.
	TrackerNumWant int
	// Time to wait for announcing stopped event.
	// Stopped event is sent to the tracker when torrent is stopped.
	TrackerStopTimeout time.Duration
	// When the client needs new peer addresses to connect, it ask to the tracker.
	// To prevent spamming the tracker an interval is set to wait before the next announce.
	TrackerMinAnnounceInterval time.Duration
	// Total time to wait for response to be read.
	// This includes ConnectTimeout and TLSHandshakeTimeout.
	TrackerHTTPTimeout time.Duration
	// User agent sent when communicating with HTTP trackers.
	TrackerHTTPUserAgent string

	// Number of unchoked peers.
	UnchokedPeers int
	// Number of optimistic unchoked peers.
	OptimisticUnchokedPeers int
	// Max number of blocks requested from a peer but not received yet
	RequestQueueLength int
	// Time to wait for a requested block to be received before marking peer as snubbed
	RequestTimeout time.Duration
	// Max number of running downloads on piece in endgame mode, snubbed and choed peers don't count
	EndgameMaxDuplicateDownloads int
	// Max number of outgoing connections to dial
	MaxPeerDial int
	// Max number of incoming connections to accept
	MaxPeerAccept       int
	MaxActivePieceBytes int64
	// Running metadata downloads, snubbed peers don't count
	ParallelMetadataDownloads int
	// Time to wait for TCP connection to open.
	PeerConnectTimeout time.Duration
	// Time to wait for BitTorrent handshake to complete.
	PeerHandshakeTimeout time.Duration
	// When peer has started to send piece block, if it does not send any bytes in PieceReadTimeout, the connection is closed.
	PieceReadTimeout time.Duration
	// Buffer size for messages read from a single peer
	PeerReadBufferSize int
	// Max number of peer addresses to keep in connect queue.
	MaxPeerAddresses int

	// Number of bytes to read when a piece is requested by a peer.
	PieceReadSize int64
	// Number of cached bytes for piece read requests.
	PieceCacheSize int64
	// Read bytes for a piece part expires after duration.
	PieceCacheTTL time.Duration

	// When the client want to connect a peer, first it tries to do encrypted handshake.
	// If it does not work, it connects to same peer again and does unencrypted handshake.
	// This behavior can be changed via this variable.
	DisableOutgoingEncryption bool
	// Dial only encrypted connections.
	ForceOutgoingEncryption bool
	// Do not accept unencrypted connections.
	ForceIncomingEncryption bool
}

var DefaultConfig = Config{
	// Session
	Database:                        "~/rain/session.db",
	DataDir:                         "~/rain/data",
	PortBegin:                       50000,
	PortEnd:                         60000,
	MaxOpenFiles:                    1000000,
	PEXEnabled:                      true,
	BitfieldWriteInterval:           30 * time.Second,
	StatsWriteInterval:              30 * time.Second,
	PeerIDPrefix:                    "-RN" + Version + "-",
	ExtensionHandshakeClientVersion: "Rain " + Version,
	BlocklistUpdateInterval:         24 * time.Hour,

	// RPC Server
	RPCEnabled:         true,
	RPCHost:            "127.0.0.1",
	RPCPort:            7246,
	RPCShutdownTimeout: 5 * time.Second,

	// Tracker
	TrackerNumWant:             100,
	TrackerStopTimeout:         5 * time.Second,
	TrackerMinAnnounceInterval: time.Minute,
	TrackerHTTPTimeout:         10 * time.Second,
	TrackerHTTPUserAgent:       "Rain/" + Version,

	// DHT node
	DHTEnabled:             true,
	DHTAddress:             "0.0.0.0",
	DHTPort:                7246,
	DHTAnnounceInterval:    30 * time.Minute,
	DHTMinAnnounceInterval: time.Minute,

	// Peer
	UnchokedPeers:                3,
	OptimisticUnchokedPeers:      1,
	RequestQueueLength:           50,
	RequestTimeout:               20 * time.Second,
	EndgameMaxDuplicateDownloads: 20,
	MaxPeerDial:                  80,
	MaxPeerAccept:                20,
	MaxActivePieceBytes:          1024 * 1024 * 1024,
	ParallelMetadataDownloads:    2,
	PeerConnectTimeout:           5 * time.Second,
	PeerHandshakeTimeout:         10 * time.Second,
	PieceReadTimeout:             30 * time.Second,
	PeerReadBufferSize:           16 * 1024,
	MaxPeerAddresses:             2000,

	// Piece cache
	PieceReadSize:  256 * 1024,
	PieceCacheSize: 256 * 1024 * 1024,
	PieceCacheTTL:  5 * time.Minute,
}
<commit_msg>reduce peer read buffer size<commit_after>package torrent

import "time"

// Config for Session.
type Config struct {
	// Database file to save resume data.
	Database string
	// DataDir is where files are downloaded.
	DataDir string
	// New torrents will be listened at selected port in this range.
	PortBegin, PortEnd uint16
	// At start, client will set max open files limit to this number. (like "ulimit -n" command)
	MaxOpenFiles uint64
	// Enable peer exchange protocol.
	PEXEnabled bool
	// Bitfield is saved to disk for fast resume without hash checking.
	// There is an interval to keep IO lower.
	BitfieldWriteInterval time.Duration
	// Stats are written at interval to reduce write operations.
	StatsWriteInterval time.Duration
	// Peer id is prefixed with this string. See BEP 20. Remaining bytes of peer id will be randomized.
	PeerIDPrefix string
	// Client version that is sent in BEP 10 handshake message.
	ExtensionHandshakeClientVersion string
	// URL to the blocklist file in CIDR format.
	BlocklistURL string
	// When to refresh blocklist
	BlocklistUpdateInterval time.Duration

	// Enable RPC server
	RPCEnabled bool
	// Host to listen for RPC server
	RPCHost string
	// Listen port for RPC server
	RPCPort int
	// Time to wait for ongoing requests before shutting down RPC HTTP server.
	RPCShutdownTimeout time.Duration

	// Enable DHT node.
	DHTEnabled bool
	// DHT node will listen on this IP.
	DHTAddress string
	// DHT node will listen on this UDP port.
	DHTPort uint16
	// DHT announce interval
	DHTAnnounceInterval time.Duration
	// Minimum announce interval when announcing to DHT.
	DHTMinAnnounceInterval time.Duration

	// Number of peer addresses to request in announce request.
	TrackerNumWant int
	// Time to wait for announcing stopped event.
	// Stopped event is sent to the tracker when torrent is stopped.
	TrackerStopTimeout time.Duration
	// When the client needs new peer addresses to connect, it ask to the tracker.
	// To prevent spamming the tracker an interval is set to wait before the next announce.
	TrackerMinAnnounceInterval time.Duration
	// Total time to wait for response to be read.
	// This includes ConnectTimeout and TLSHandshakeTimeout.
	TrackerHTTPTimeout time.Duration
	// User agent sent when communicating with HTTP trackers.
	TrackerHTTPUserAgent string

	// Number of unchoked peers.
	UnchokedPeers int
	// Number of optimistic unchoked peers.
	OptimisticUnchokedPeers int
	// Max number of blocks requested from a peer but not received yet
	RequestQueueLength int
	// Time to wait for a requested block to be received before marking peer as snubbed
	RequestTimeout time.Duration
	// Max number of running downloads on piece in endgame mode, snubbed and choed peers don't count
	EndgameMaxDuplicateDownloads int
	// Max number of outgoing connections to dial
	MaxPeerDial int
	// Max number of incoming connections to accept
	MaxPeerAccept       int
	MaxActivePieceBytes int64
	// Running metadata downloads, snubbed peers don't count
	ParallelMetadataDownloads int
	// Time to wait for TCP connection to open.
	PeerConnectTimeout time.Duration
	// Time to wait for BitTorrent handshake to complete.
	PeerHandshakeTimeout time.Duration
	// When peer has started to send piece block, if it does not send any bytes in PieceReadTimeout, the connection is closed.
	PieceReadTimeout time.Duration
	// Buffer size for messages read from a single peer
	PeerReadBufferSize int
	// Max number of peer addresses to keep in connect queue.
	MaxPeerAddresses int

	// Number of bytes to read when a piece is requested by a peer.
	PieceReadSize int64
	// Number of cached bytes for piece read requests.
	PieceCacheSize int64
	// Read bytes for a piece part expires after duration.
	PieceCacheTTL time.Duration

	// When the client want to connect a peer, first it tries to do encrypted handshake.
	// If it does not work, it connects to same peer again and does unencrypted handshake.
	// This behavior can be changed via this variable.
	DisableOutgoingEncryption bool
	// Dial only encrypted connections.
	ForceOutgoingEncryption bool
	// Do not accept unencrypted connections.
	ForceIncomingEncryption bool
}

var DefaultConfig = Config{
	// Session
	Database:                        "~/rain/session.db",
	DataDir:                         "~/rain/data",
	PortBegin:                       50000,
	PortEnd:                         60000,
	MaxOpenFiles:                    1000000,
	PEXEnabled:                      true,
	BitfieldWriteInterval:           30 * time.Second,
	StatsWriteInterval:              30 * time.Second,
	PeerIDPrefix:                    "-RN" + Version + "-",
	ExtensionHandshakeClientVersion: "Rain " + Version,
	BlocklistUpdateInterval:         24 * time.Hour,

	// RPC Server
	RPCEnabled:         true,
	RPCHost:            "127.0.0.1",
	RPCPort:            7246,
	RPCShutdownTimeout: 5 * time.Second,

	// Tracker
	TrackerNumWant:             100,
	TrackerStopTimeout:         5 * time.Second,
	TrackerMinAnnounceInterval: time.Minute,
	TrackerHTTPTimeout:         10 * time.Second,
	TrackerHTTPUserAgent:       "Rain/" + Version,

	// DHT node
	DHTEnabled:             true,
	DHTAddress:             "0.0.0.0",
	DHTPort:                7246,
	DHTAnnounceInterval:    30 * time.Minute,
	DHTMinAnnounceInterval: time.Minute,

	// Peer
	UnchokedPeers:                3,
	OptimisticUnchokedPeers:      1,
	RequestQueueLength:           50,
	RequestTimeout:               20 * time.Second,
	EndgameMaxDuplicateDownloads: 20,
	MaxPeerDial:                  80,
	MaxPeerAccept:                20,
	MaxActivePieceBytes:          1024 * 1024 * 1024,
	ParallelMetadataDownloads:    2,
	PeerConnectTimeout:           5 * time.Second,
	PeerHandshakeTimeout:         10 * time.Second,
	PieceReadTimeout:             30 * time.Second,
	PeerReadBufferSize:           17,
	MaxPeerAddresses:             2000,

	// Piece cache
	PieceReadSize:  256 * 1024,
	PieceCacheSize: 256 * 1024 * 1024,
	PieceCacheTTL:  5 * time.Minute,
}
<|endoftext|>


<commit_before>package api_test

import (
	. "cf/api"
	"cf/errors"
	"cf/models"
	"cf/net"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"net/http"
	"net/http/httptest"
	testapi "testhelpers/api"
	testconfig "testhelpers/configuration"
	testnet "testhelpers/net"
)

var _ = Describe("route repository", func() {
	It("lists routes", func() {
		firstRequest := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "GET",
			Path:     "/v2/routes?inline-relations-depth=1",
			Response: firstPageRoutesResponse,
		})

		secondRequest := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "GET",
			Path:     "/v2/routes?inline-relations-depth=1&page=2",
			Response: secondPageRoutesResponse,
		})

		ts, handler, repo, _ := createRoutesRepo(firstRequest, secondRequest)
		defer ts.Close()

		routes := []models.Route{}
		apiErr := repo.ListRoutes(func(route models.Route) bool {
			routes = append(routes, route)
			return true
		})

		Expect(len(routes)).To(Equal(2))
		Expect(routes[0].Guid).To(Equal("route-1-guid"))
		Expect(routes[1].Guid).To(Equal("route-2-guid"))
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
	})

	It("finds routes by host", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "GET",
			Path:     "/v2/routes?q=host%3Amy-cool-app",
			Response: findRouteByHostResponse,
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		route, apiErr := repo.FindByHost("my-cool-app")

		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
		Expect(route.Host).To(Equal("my-cool-app"))
		Expect(route.Guid).To(Equal("my-route-guid"))
	})

	It("returns an error when a route is not found with the given host", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "GET",
			Path:     "/v2/routes?q=host%3Amy-cool-app",
			Response: testnet.TestResponse{Status: http.StatusCreated, Body: ` { "resources": [ ]}`},
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		_, apiErr := repo.FindByHost("my-cool-app")

		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(BeNil())
	})

	It("finds a route by host and domain", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "GET",
			Path:     "/v2/routes?q=host%3Amy-cool-app%3Bdomain_guid%3Amy-domain-guid",
			Response: findRouteByHostResponse,
		})

		ts, handler, repo, domainRepo := createRoutesRepo(request)
		defer ts.Close()

		domain := models.DomainFields{}
		domain.Guid = "my-domain-guid"
		domainRepo.FindByNameDomain = domain

		route, apiErr := repo.FindByHostAndDomain("my-cool-app", "my-domain.com")

		Expect(apiErr).NotTo(HaveOccurred())
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(domainRepo.FindByNameName).To(Equal("my-domain.com"))
		Expect(route.Host).To(Equal("my-cool-app"))
		Expect(route.Guid).To(Equal("my-route-guid"))
		Expect(route.Domain.Guid).To(Equal(domain.Guid))
	})

	It("returns 'not found' response when there is no route w/ the given domain and host", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "GET",
			Path:     "/v2/routes?q=host%3Amy-cool-app%3Bdomain_guid%3Amy-domain-guid",
			Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [ ] }`},
		})

		ts, handler, repo, domainRepo := createRoutesRepo(request)
		defer ts.Close()

		domain := models.DomainFields{}
		domain.Guid = "my-domain-guid"
		domainRepo.FindByNameDomain = domain

		_, apiErr := repo.FindByHostAndDomain("my-cool-app", "my-domain.com")

		Expect(handler).To(testnet.HaveAllRequestsCalled())

		Expect(apiErr.(*errors.ModelNotFoundError)).NotTo(BeNil())
	})

	It("creates routes in a given space", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:  "POST",
			Path:    "/v2/routes?inline-relations-depth=1",
			Matcher: testnet.RequestBodyMatcher(`{"host":"my-cool-app","domain_guid":"my-domain-guid","space_guid":"my-space-guid"}`),
			Response: testnet.TestResponse{Status: http.StatusCreated, Body: `
{
"metadata": { "guid": "my-route-guid" },
"entity": { "host": "my-cool-app" }
}`},
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		createdRoute, apiErr := repo.CreateInSpace("my-cool-app", "my-domain-guid", "my-space-guid")

		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
		Expect(createdRoute.Guid).To(Equal("my-route-guid"))
	})

	It("creates routes", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:  "POST",
			Path:    "/v2/routes?inline-relations-depth=1",
			Matcher: testnet.RequestBodyMatcher(`{"host":"my-cool-app","domain_guid":"my-domain-guid","space_guid":"my-space-guid"}`),
			Response: testnet.TestResponse{Status: http.StatusCreated, Body: `
{
"metadata": { "guid": "my-route-guid" },
"entity": { "host": "my-cool-app" }
}`},
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		createdRoute, apiErr := repo.Create("my-cool-app", "my-domain-guid")
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())

		Expect(createdRoute.Guid).To(Equal("my-route-guid"))
	})

	It("binds routes", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "PUT",
			Path:     "/v2/apps/my-cool-app-guid/routes/my-cool-route-guid",
			Response: testnet.TestResponse{Status: http.StatusCreated, Body: ""},
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		apiErr := repo.Bind("my-cool-route-guid", "my-cool-app-guid")
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
	})

	It("unbinds routes", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "DELETE",
			Path:     "/v2/apps/my-cool-app-guid/routes/my-cool-route-guid",
			Response: testnet.TestResponse{Status: http.StatusCreated, Body: ""},
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		apiErr := repo.Unbind("my-cool-route-guid", "my-cool-app-guid")
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
	})

	It("deletes routes", func() {
		request := testapi.NewCloudControllerTestRequest(testnet.TestRequest{
			Method:   "DELETE",
			Path:     "/v2/routes/my-cool-route-guid",
			Response: testnet.TestResponse{Status: http.StatusCreated, Body: ""},
		})

		ts, handler, repo, _ := createRoutesRepo(request)
		defer ts.Close()

		apiErr := repo.Delete("my-cool-route-guid")
		Expect(handler).To(testnet.HaveAllRequestsCalled())
		Expect(apiErr).NotTo(HaveOccurred())
	})
})

var firstPageRoutesResponse = testnet.TestResponse{Status: http.StatusOK, Body: `
{
  "next_url": "/v2/routes?inline-relations-depth=1&page=2",
  "resources": [
    {
      "metadata": {
        "guid": "route-1-guid"
      },
      "entity": {
        "host": "route-1-host",
        "domain": {
          "metadata": {
            "guid": "domain-1-guid"
          },
          "entity": {
            "name": "cfapps.io"
          }
        },
        "space": {
          "metadata": {
            "guid": "space-1-guid"
          },
          "entity": {
            "name": "space-1"
          }
        },
        "apps": [
       	  {
       	    "metadata": {
              "guid": "app-1-guid"
            },
            "entity": {
              "name": "app-1"
       	    }
       	  }
        ]
      }
    }
  ]
}`}

var secondPageRoutesResponse = testnet.TestResponse{Status: http.StatusOK, Body: `
{
  "resources": [
    {
      "metadata": {
        "guid": "route-2-guid"
      },
      "entity": {
        "host": "route-2-host",
        "domain": {
          "metadata": {
            "guid": "domain-2-guid"
          },
          "entity": {
            "name": "example.com"
          }
        },
        "space": {
          "metadata": {
            "guid": "space-2-guid"
          },
          "entity": {
            "name": "space-2"
          }
        },
        "apps": [
       	  {
       	    "metadata": {
              "guid": "app-2-guid"
            },
            "entity": {
              "name": "app-2"
       	    }
       	  },
       	  {
       	    "metadata": {
              "guid": "app-3-guid"
            },
            "entity": {
              "name": "app-3"
       	    }
       	  }
        ]
      }
    }
  ]
}`}

var findRouteByHostResponse = testnet.TestResponse{Status: http.StatusCreated, Body: `
{ "resources": [
    {
    	"metadata": {
        	"guid": "my-route-guid"
    	},
    	"entity": {
       	     "host": "my-cool-app",
       	     "domain": {
       	     	"metadata": {
       	     		"guid": "my-domain-guid"
       	     	}
       	     }
    	}
    }
]}`}

func createRoutesRepo(requests ...testnet.TestRequest) (ts *httptest.Server, handler *testnet.TestHandler, repo CloudControllerRouteRepository, domainRepo *testapi.FakeDomainRepository) {
	ts, handler = testnet.NewServer(requests)

	configRepo := testconfig.NewRepositoryWithDefaults()
	configRepo.SetApiEndpoint(ts.URL)
	gateway := net.NewCloudControllerGateway(configRepo)
	domainRepo = &testapi.FakeDomainRepository{}

	repo = NewCloudControllerRouteRepository(configRepo, gateway, domainRepo)
	return
}
<commit_msg>Clean up routes test<commit_after>package api_test

import (
	. "cf/api"
	"cf/configuration"
	"cf/errors"
	"cf/models"
	"cf/net"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"net/http"
	"net/http/httptest"
	testapi "testhelpers/api"
	testconfig "testhelpers/configuration"
	testnet "testhelpers/net"
)

var _ = Describe("route repository", func() {

	var (
		ts         *httptest.Server
		handler    *testnet.TestHandler
		configRepo configuration.Repository
		domainRepo *testapi.FakeDomainRepository
		repo       CloudControllerRouteRepository
	)

	BeforeEach(func() {
		configRepo = testconfig.NewRepositoryWithDefaults()
		gateway := net.NewCloudControllerGateway(configRepo)
		domainRepo = &testapi.FakeDomainRepository{}
		repo = NewCloudControllerRouteRepository(configRepo, gateway, domainRepo)
	})

	AfterEach(func() {
		ts.Close()
	})

	Describe("List routes", func() {
		It("lists routes", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "GET",
					Path:     "/v2/routes?inline-relations-depth=1",
					Response: firstPageRoutesResponse,
				}),
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "GET",
					Path:     "/v2/routes?inline-relations-depth=1&page=2",
					Response: secondPageRoutesResponse,
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			routes := []models.Route{}
			apiErr := repo.ListRoutes(func(route models.Route) bool {
				routes = append(routes, route)
				return true
			})

			Expect(len(routes)).To(Equal(2))
			Expect(routes[0].Guid).To(Equal("route-1-guid"))
			Expect(routes[1].Guid).To(Equal("route-2-guid"))
			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())
		})

		It("finds routes by host", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "GET",
					Path:     "/v2/routes?q=host%3Amy-cool-app",
					Response: findRouteByHostResponse,
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			route, apiErr := repo.FindByHost("my-cool-app")

			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())
			Expect(route.Host).To(Equal("my-cool-app"))
			Expect(route.Guid).To(Equal("my-route-guid"))
		})

		It("returns an error when a route is not found with the given host", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "GET",
					Path:     "/v2/routes?q=host%3Amy-cool-app",
					Response: testnet.TestResponse{Status: http.StatusCreated, Body: ` { "resources": [ ]}`},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			_, apiErr := repo.FindByHost("my-cool-app")

			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(BeNil())
		})

		It("finds a route by host and domain", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "GET",
					Path:     "/v2/routes?q=host%3Amy-cool-app%3Bdomain_guid%3Amy-domain-guid",
					Response: findRouteByHostResponse,
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			domain := models.DomainFields{}
			domain.Guid = "my-domain-guid"
			domainRepo.FindByNameDomain = domain

			route, apiErr := repo.FindByHostAndDomain("my-cool-app", "my-domain.com")

			Expect(apiErr).NotTo(HaveOccurred())
			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(domainRepo.FindByNameName).To(Equal("my-domain.com"))
			Expect(route.Host).To(Equal("my-cool-app"))
			Expect(route.Guid).To(Equal("my-route-guid"))
			Expect(route.Domain.Guid).To(Equal(domain.Guid))
		})

		It("returns 'not found' response when there is no route w/ the given domain and host", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "GET",
					Path:     "/v2/routes?q=host%3Amy-cool-app%3Bdomain_guid%3Amy-domain-guid",
					Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [ ] }`},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			domain := models.DomainFields{}
			domain.Guid = "my-domain-guid"
			domainRepo.FindByNameDomain = domain

			_, apiErr := repo.FindByHostAndDomain("my-cool-app", "my-domain.com")

			Expect(handler).To(testnet.HaveAllRequestsCalled())

			Expect(apiErr.(*errors.ModelNotFoundError)).NotTo(BeNil())
		})
	})

	Describe("Create routes", func() {
		It("creates routes in a given space", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:  "POST",
					Path:    "/v2/routes?inline-relations-depth=1",
					Matcher: testnet.RequestBodyMatcher(`{"host":"my-cool-app","domain_guid":"my-domain-guid","space_guid":"my-space-guid"}`),
					Response: testnet.TestResponse{Status: http.StatusCreated, Body: `
						{
							"metadata": { "guid": "my-route-guid" },
							"entity": { "host": "my-cool-app" }
						}
					`},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			createdRoute, apiErr := repo.CreateInSpace("my-cool-app", "my-domain-guid", "my-space-guid")

			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())
			Expect(createdRoute.Guid).To(Equal("my-route-guid"))
		})

		It("creates routes", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:  "POST",
					Path:    "/v2/routes?inline-relations-depth=1",
					Matcher: testnet.RequestBodyMatcher(`{"host":"my-cool-app","domain_guid":"my-domain-guid","space_guid":"my-space-guid"}`),
					Response: testnet.TestResponse{Status: http.StatusCreated, Body: `
						{
							"metadata": { "guid": "my-route-guid" },
							"entity": { "host": "my-cool-app" }
						}
					`},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			createdRoute, apiErr := repo.Create("my-cool-app", "my-domain-guid")
			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())

			Expect(createdRoute.Guid).To(Equal("my-route-guid"))
		})

	})

	Describe("Bind routes", func() {
		It("binds routes", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "PUT",
					Path:     "/v2/apps/my-cool-app-guid/routes/my-cool-route-guid",
					Response: testnet.TestResponse{Status: http.StatusCreated, Body: ""},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			apiErr := repo.Bind("my-cool-route-guid", "my-cool-app-guid")
			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())
		})

		It("unbinds routes", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "DELETE",
					Path:     "/v2/apps/my-cool-app-guid/routes/my-cool-route-guid",
					Response: testnet.TestResponse{Status: http.StatusCreated, Body: ""},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			apiErr := repo.Unbind("my-cool-route-guid", "my-cool-app-guid")
			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())
		})

	})

	Describe("Delete routes", func() {
		It("deletes routes", func() {
			ts, handler = testnet.NewServer([]testnet.TestRequest{
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method:   "DELETE",
					Path:     "/v2/routes/my-cool-route-guid",
					Response: testnet.TestResponse{Status: http.StatusCreated, Body: ""},
				}),
			})
			configRepo.SetApiEndpoint(ts.URL)

			apiErr := repo.Delete("my-cool-route-guid")
			Expect(handler).To(testnet.HaveAllRequestsCalled())
			Expect(apiErr).NotTo(HaveOccurred())
		})
	})

})

var firstPageRoutesResponse = testnet.TestResponse{Status: http.StatusOK, Body: `
{
  "next_url": "/v2/routes?inline-relations-depth=1&page=2",
  "resources": [
    {
      "metadata": {
        "guid": "route-1-guid"
      },
      "entity": {
        "host": "route-1-host",
        "domain": {
          "metadata": {
            "guid": "domain-1-guid"
          },
          "entity": {
            "name": "cfapps.io"
          }
        },
        "space": {
          "metadata": {
            "guid": "space-1-guid"
          },
          "entity": {
            "name": "space-1"
          }
        },
        "apps": [
       	  {
       	    "metadata": {
              "guid": "app-1-guid"
            },
            "entity": {
              "name": "app-1"
       	    }
       	  }
        ]
      }
    }
  ]
}`}

var secondPageRoutesResponse = testnet.TestResponse{Status: http.StatusOK, Body: `
{
  "resources": [
    {
      "metadata": {
        "guid": "route-2-guid"
      },
      "entity": {
        "host": "route-2-host",
        "domain": {
          "metadata": {
            "guid": "domain-2-guid"
          },
          "entity": {
            "name": "example.com"
          }
        },
        "space": {
          "metadata": {
            "guid": "space-2-guid"
          },
          "entity": {
            "name": "space-2"
          }
        },
        "apps": [
       	  {
       	    "metadata": {
              "guid": "app-2-guid"
            },
            "entity": {
              "name": "app-2"
       	    }
       	  },
       	  {
       	    "metadata": {
              "guid": "app-3-guid"
            },
            "entity": {
              "name": "app-3"
       	    }
       	  }
        ]
      }
    }
  ]
}`}

var findRouteByHostResponse = testnet.TestResponse{Status: http.StatusCreated, Body: `
{ "resources": [
    {
    	"metadata": {
        	"guid": "my-route-guid"
    	},
    	"entity": {
       	     "host": "my-cool-app",
       	     "domain": {
       	     	"metadata": {
       	     		"guid": "my-domain-guid"
       	     	}
       	     }
    	}
    }
]}`}
<|endoftext|>


<commit_before>package model

import (
	"encoding/base64"
	"errors"
	"github.com/DanielRenne/GoCore/core/dbServices"
	"github.com/DanielRenne/GoCore/core/serverSettings"
	"github.com/asaskevich/govalidator"
	"github.com/fatih/camelcase"
	"reflect"
	"strings"
	"sync"
	"time"
)

const (
	TRANSACTION_DATATYPE_ORIGINAL = 1
	TRANSACTION_DATATYPE_NEW      = 2

	TRANSACTION_CHANGETYPE_INSERT = 1
	TRANSACTION_CHANGETYPE_UPDATE = 2
	TRANSACTION_CHANGETYPE_DELETE = 3

	MGO_RECORD_NOT_FOUND = "not found"

	VALIDATION_ERROR                   = "ValidationError"
	VALIDATION_ERROR_REQUIRED          = "ValidationErrorRequiredFieldMissing"
	VALIDATION_ERROR_EMAIL             = "ValidationErrorInvalidEmail"
	VALIDATION_ERROR_SPECIFIC_REQUIRED = "ValidationFieldSpecificRequired"
	VALIDATION_ERROR_SPECIFIC_EMAIL    = "ValidationFieldSpecificEmailRequired"
)

type modelEntity interface {
	Save() error
	Delete() error
	SaveWithTran(*Transaction) error
	Reflect() []Field
	JoinFields(string, *Query, int) error
}

type modelCollection interface {
	Rollback(transactionId string) error
}

type collection interface {
	Query() *Query
}

type tQueue struct {
	sync.RWMutex
	queue map[string]*transactionsToPersist
}

type transactionsToPersist struct {
	t             *Transaction
	newItems      []entityTransaction
	originalItems []entityTransaction
	startTime     time.Time
}

type entityTransaction struct {
	changeType int
	committed  bool
	entity     modelEntity
}

type Field struct {
	Name       string
	Label      string
	DataType   string
	IsView     bool
	Validation *dbServices.FieldValidation
}

var transactionQueue tQueue

func init() {
	transactionQueue.queue = make(map[string]*transactionsToPersist)
	go clearTransactionQueue()
}

func Q(k string, v interface{}) map[string]interface{} {
	return map[string]interface{}{k: v}
}

func RangeQ(k string, min interface{}, max interface{}) map[string]Range {
	var rge map[string]Range
	rge = make(map[string]Range)
	rge[k] = Range{
		Max: max,
		Min: min,
	}
	return rge
}

//Every 12 hours check the transactionQueue and remove any outstanding stale transactions > 48 hours old
func clearTransactionQueue() {

	transactionQueue.Lock()

	for key, value := range transactionQueue.queue {

		if time.Since(value.startTime).Hours() > 48 {
			delete(transactionQueue.queue, key)
		}
	}

	transactionQueue.Unlock()

	time.Sleep(12 * time.Hour)
	clearTransactionQueue()
}

func getBase64(value string) string {
	return base64.StdEncoding.EncodeToString([]byte(value))
}

func decodeBase64(value string) (string, error) {
	data, err := base64.StdEncoding.DecodeString(value)
	if err != nil {
		return "", err
	}

	return string(data[:]), nil
}

func getNow() time.Time {
	return time.Now()
}

func removeDuplicates(elements []string) []string {
	// Use map to record duplicates as we find them.
	encountered := map[string]bool{}
	result := []string{}

	for v := range elements {
		if encountered[elements[v]] == true {
			// Do not add duplicate.
		} else {
			// Record this element as an encountered element.
			encountered[elements[v]] = true
			// Append to result slice.
			result = append(result, elements[v])
		}
	}
	// Return the new slice.
	return result
}

func IsValidationError(err error) bool {
	if err == nil {
		return false
	}
	if err.Error() == VALIDATION_ERROR || err.Error() == VALIDATION_ERROR_EMAIL {
		return true
	}
	return false
}

func validateFields(x interface{}, objectToUpdate interface{}, val reflect.Value) error {

	isError := false
	for key, value := range dbServices.GetValidationTags(x) {

		fieldValue := dbServices.GetReflectionFieldValue(key, objectToUpdate)
		validations := strings.Split(value, ",")

		if validations[0] != "" {
			if err := validateRequired(fieldValue, validations[0]); err != nil {
				dbServices.SetFieldValue("Errors."+key, val, VALIDATION_ERROR_SPECIFIC_REQUIRED)
				isError = true
			}
		}
		if validations[1] != "" {

			cleanup, err := validateType(fieldValue, validations[1])

			if err != nil {
				if err.Error() == VALIDATION_ERROR_EMAIL {
					dbServices.SetFieldValue("Errors."+key, val, VALIDATION_ERROR_SPECIFIC_EMAIL)
				}
				isError = true
			}

			if cleanup != "" {
				dbServices.SetFieldValue(key, val, cleanup)
			}

		}

	}

	if isError {
		return errors.New(VALIDATION_ERROR)
	}

	return nil
}

func validateRequired(value string, tagValue string) error {
	if tagValue == "true" {
		if value == "" {
			return errors.New(VALIDATION_ERROR_REQUIRED)
		}
		return nil
	}
	return nil
}

func validateType(value string, tagValue string) (string, error) {
	switch tagValue {
	case dbServices.VALIDATION_TYPE_EMAIL:
		return "", validateEmail(value)
	}
	return "", nil
}

func validateEmail(value string) error {
	if !govalidator.IsEmail(value) {
		return errors.New(VALIDATION_ERROR_EMAIL)
	}
	return nil
}

func getJoins(x reflect.Value, remainingRecursions string) (joins []join) {
	if remainingRecursions == "" {
		return
	}

	fields := strings.Split(remainingRecursions, ".")
	fieldName := fields[0]

	joinsField := x.FieldByName("Joins")

	if joinsField.Kind() != reflect.Struct {
		return
	}

	if fieldName == JOIN_ALL {
		for i := 0; i < joinsField.NumField(); i++ {

			typeField := joinsField.Type().Field(i)
			name := typeField.Name
			tagValue := typeField.Tag.Get("join")
			splitValue := strings.Split(tagValue, ",")
			var j join
			j.collectionName = splitValue[0]
			j.joinSchemaName = splitValue[1]
			j.joinFieldRefName = splitValue[2]
			j.joinFieldName = name
			j.joinSpecified = JOIN_ALL
			joins = append(joins, j)
		}
	} else {
		typeField, ok := joinsField.Type().FieldByName(fieldName)
		if ok == false {
			return
		}
		name := typeField.Name
		tagValue := typeField.Tag.Get("join")
		splitValue := strings.Split(tagValue, ",")
		var j join
		j.collectionName = splitValue[0]
		j.joinSchemaName = splitValue[1]
		j.joinFieldRefName = splitValue[2]
		j.joinFieldName = name
		j.joinSpecified = strings.Replace(remainingRecursions, fieldName+".", "", 1)
		if strings.Contains(j.joinSpecified, "Count") && j.joinSpecified[:5] == "Count" {
			j.joinSpecified = "Count"
		}
		joins = append(joins, j)
	}
	return
}

func IsZeroOfUnderlyingType(x interface{}) bool {
	return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

func Reflect(obj interface{}) []Field {
	var ret []Field
	val := reflect.ValueOf(obj)

	for i := 0; i < val.NumField(); i++ {
		typeField := val.Type().Field(i)
		if typeField.Name != "Errors" && typeField.Name != "Joins" && typeField.Name != "Id" {
			if typeField.Name == "Views" {
				for f := 0; f < val.FieldByName("Views").NumField(); f++ {
					field := Field{}
					field.IsView = true
					name := val.FieldByName("Views").Type().Field(f).Name
					namePart := camelcase.Split(name)
					for x := 0; x < len(namePart); x++ {
						if x > 0 {
							namePart[x] = strings.ToLower(namePart[x])
						}
					}
					field.Name = val.FieldByName("Views").Type().Field(f).Name
					field.Label = strings.Join(namePart[:], " ")
					field.DataType = val.FieldByName("Views").Type().Field(f).Type.Name()
					validate := val.FieldByName("Views").Type().Field(f).Tag.Get("validate")
					if validate != "" {
						//core.Debug.Dump(validate)
						//parts := strings.Split(validate, ",")
						//field.Validation.Required = true//extensions.StringToBool(parts[0])
						//field.Validation.Type = parts[1]
						//field.Validation.Min = parts[2]
						//field.Validation.Max = parts[3]
						//field.Validation.Length = parts[4]
						//field.Validation.LengthMax = parts[4]
						//field.Validation.LengthMin = parts[5]
					}
					ret = append(ret, field)
				}
			} else {
				field := Field{}
				validate := typeField.Tag.Get("validate")
				if validate != "" {
					//core.Debug.Dump(validate)
					//parts := strings.Split(validate, ",")
					//core.Debug.Dump(extensions.StringToBool(parts[0]))
					//field.Validation.Required = extensions.StringToBool(parts[0])
					//field.Validation.Type = parts[1]
					//field.Validation.Min = parts[2]
					//field.Validation.Max = parts[3]
					//field.Validation.Length = parts[4]
					//field.Validation.LengthMax = parts[4]
					//field.Validation.LengthMin = parts[5]
				}
				name := typeField.Name
				namePart := camelcase.Split(name)
				for x := 0; x < len(namePart); x++ {
					if x > 0 {
						namePart[x] = strings.ToLower(namePart[x])
					}
				}
				field.Name = typeField.Name
				field.Label = strings.Join(namePart[:], " ")
				field.DataType = typeField.Type.Name()
				ret = append(ret, field)
			}
		}
	}
	return ret
}

func JoinEntity(collectionQ *Query, y interface{}, j join, id string, fieldToSet reflect.Value, remainingRecursions string, q *Query, endRecursion bool, recursionCount int) (err error) {
	if IsZeroOfUnderlyingType(fieldToSet.Interface()) || j.isMany {

		if j.isMany {
			if remainingRecursions == "Count" {
				cnt, err := collectionQ.ToggleLogFlag(true).Filter(Q(j.joinForeignFieldName, id)).Count()
				if serverSettings.WebConfig.Application.LogJoinQueries {
					collectionQ.LogQuery("JoinEntity() Recursion Count Only")
				}
				if err != nil {
					// err = errCnt
					return err
				}
				countField := fieldToSet.Elem().FieldByName("Count")
				countField.Set(reflect.ValueOf(cnt))
				return err
			}
			err = collectionQ.ToggleLogFlag(true).Filter(Q(j.joinForeignFieldName, id)).All(y)
			if serverSettings.WebConfig.Application.LogJoinQueries {
				collectionQ.LogQuery("JoinEntity({" + j.joinForeignFieldName + ": " + id + "}) Recursion Many")
			}
		} else {
			if j.joinForeignFieldName == "" {
				err = collectionQ.ToggleLogFlag(true).ById(id, y)
				if serverSettings.WebConfig.Application.LogJoinQueries {
					collectionQ.LogQuery("JoinEntity() Recursion Single By Id (" + id + ")")
				}
			} else {
				err = collectionQ.ToggleLogFlag(true).Filter(Q(j.joinForeignFieldName, id)).One(y)
				if serverSettings.WebConfig.Application.LogJoinQueries {
					collectionQ.LogQuery("JoinEntity({" + j.joinForeignFieldName + ": " + id + "}) Recursion Single")
				}
			}
		}

		if err == nil {
			if endRecursion == false && recursionCount > 0 {
				recursionCount--

				in := []reflect.Value{}
				in = append(in, reflect.ValueOf(remainingRecursions))
				in = append(in, reflect.ValueOf(q))
				in = append(in, reflect.ValueOf(recursionCount))

				if j.isMany {

					myArray := reflect.ValueOf(y).Elem()
					for i := 0; i < myArray.Len(); i++ {
						s := myArray.Index(i)
						err = CallMethod(s.Interface(), "JoinFields", in)
					}
				} else {
					err = CallMethod(y, "JoinFields", in)
				}

			}
			if err == nil {
				if j.isMany {

					itemsField := fieldToSet.Elem().FieldByName("Items")
					countField := fieldToSet.Elem().FieldByName("Count")
					itemsField.Set(reflect.ValueOf(y))
					countField.Set(reflect.ValueOf(reflect.ValueOf(y).Elem().Len()))
				} else {
					fieldToSet.Set(reflect.ValueOf(y))
				}

				if q.renderViews {
					err = q.processViews(y)
					if err != nil {
						return
					}
				}

			}
		}
	} else {
		if endRecursion == false && recursionCount > 0 {
			recursionCount--
			method := fieldToSet.MethodByName("JoinFields")
			in := []reflect.Value{}
			in = append(in, reflect.ValueOf(remainingRecursions))
			in = append(in, reflect.ValueOf(q))
			in = append(in, reflect.ValueOf(recursionCount))
			values := method.Call(in)
			if values[0].Interface() == nil {
				err = nil
				return
			}
			err = values[0].Interface().(error)
		}
	}
	return
}

func CallMethod(i interface{}, methodName string, in []reflect.Value) (err error) {
	var ptr reflect.Value
	var value reflect.Value
	var finalMethod reflect.Value

	value = reflect.ValueOf(i)

	// if we start with a pointer, we need to get value pointed to
	// if we start with a value, we need to get a pointer to that value
	if value.Type().Kind() == reflect.Ptr {
		ptr = value
		value = ptr.Elem()
	} else {
		ptr = reflect.New(reflect.TypeOf(i))
		temp := ptr.Elem()
		temp.Set(value)
	}

	// check for method on value
	method := value.MethodByName(methodName)
	if method.IsValid() {
		finalMethod = method
	}
	// check for method on pointer
	method = ptr.MethodByName(methodName)
	if method.IsValid() {
		finalMethod = method
	}

	if finalMethod.IsValid() {
		values := finalMethod.Call(in)
		if values[0].Interface() == nil {
			err = nil
			return
		}
		err = values[0].Interface().(error)
		return
	}

	// return or panic, method not found of either type
	return nil
}

// Start of autogenerated code....
<commit_msg>Fixed Many Join Bug.<commit_after>package model

import (
	"encoding/base64"
	"errors"
	"github.com/DanielRenne/GoCore/core/dbServices"
	"github.com/DanielRenne/GoCore/core/serverSettings"
	"github.com/asaskevich/govalidator"
	"github.com/fatih/camelcase"
	"reflect"
	"strings"
	"sync"
	"time"
)

const (
	TRANSACTION_DATATYPE_ORIGINAL = 1
	TRANSACTION_DATATYPE_NEW      = 2

	TRANSACTION_CHANGETYPE_INSERT = 1
	TRANSACTION_CHANGETYPE_UPDATE = 2
	TRANSACTION_CHANGETYPE_DELETE = 3

	MGO_RECORD_NOT_FOUND = "not found"

	VALIDATION_ERROR                   = "ValidationError"
	VALIDATION_ERROR_REQUIRED          = "ValidationErrorRequiredFieldMissing"
	VALIDATION_ERROR_EMAIL             = "ValidationErrorInvalidEmail"
	VALIDATION_ERROR_SPECIFIC_REQUIRED = "ValidationFieldSpecificRequired"
	VALIDATION_ERROR_SPECIFIC_EMAIL    = "ValidationFieldSpecificEmailRequired"
)

type modelEntity interface {
	Save() error
	Delete() error
	SaveWithTran(*Transaction) error
	Reflect() []Field
	JoinFields(string, *Query, int) error
}

type modelCollection interface {
	Rollback(transactionId string) error
}

type collection interface {
	Query() *Query
}

type tQueue struct {
	sync.RWMutex
	queue map[string]*transactionsToPersist
}

type transactionsToPersist struct {
	t             *Transaction
	newItems      []entityTransaction
	originalItems []entityTransaction
	startTime     time.Time
}

type entityTransaction struct {
	changeType int
	committed  bool
	entity     modelEntity
}

type Field struct {
	Name       string
	Label      string
	DataType   string
	IsView     bool
	Validation *dbServices.FieldValidation
}

var transactionQueue tQueue

func init() {
	transactionQueue.queue = make(map[string]*transactionsToPersist)
	go clearTransactionQueue()
}

func Q(k string, v interface{}) map[string]interface{} {
	return map[string]interface{}{k: v}
}

func RangeQ(k string, min interface{}, max interface{}) map[string]Range {
	var rge map[string]Range
	rge = make(map[string]Range)
	rge[k] = Range{
		Max: max,
		Min: min,
	}
	return rge
}

//Every 12 hours check the transactionQueue and remove any outstanding stale transactions > 48 hours old
func clearTransactionQueue() {

	transactionQueue.Lock()

	for key, value := range transactionQueue.queue {

		if time.Since(value.startTime).Hours() > 48 {
			delete(transactionQueue.queue, key)
		}
	}

	transactionQueue.Unlock()

	time.Sleep(12 * time.Hour)
	clearTransactionQueue()
}

func getBase64(value string) string {
	return base64.StdEncoding.EncodeToString([]byte(value))
}

func decodeBase64(value string) (string, error) {
	data, err := base64.StdEncoding.DecodeString(value)
	if err != nil {
		return "", err
	}

	return string(data[:]), nil
}

func getNow() time.Time {
	return time.Now()
}

func removeDuplicates(elements []string) []string {
	// Use map to record duplicates as we find them.
	encountered := map[string]bool{}
	result := []string{}

	for v := range elements {
		if encountered[elements[v]] == true {
			// Do not add duplicate.
		} else {
			// Record this element as an encountered element.
			encountered[elements[v]] = true
			// Append to result slice.
			result = append(result, elements[v])
		}
	}
	// Return the new slice.
	return result
}

func IsValidationError(err error) bool {
	if err == nil {
		return false
	}
	if err.Error() == VALIDATION_ERROR || err.Error() == VALIDATION_ERROR_EMAIL {
		return true
	}
	return false
}

func validateFields(x interface{}, objectToUpdate interface{}, val reflect.Value) error {

	isError := false
	for key, value := range dbServices.GetValidationTags(x) {

		fieldValue := dbServices.GetReflectionFieldValue(key, objectToUpdate)
		validations := strings.Split(value, ",")

		if validations[0] != "" {
			if err := validateRequired(fieldValue, validations[0]); err != nil {
				dbServices.SetFieldValue("Errors."+key, val, VALIDATION_ERROR_SPECIFIC_REQUIRED)
				isError = true
			}
		}
		if validations[1] != "" {

			cleanup, err := validateType(fieldValue, validations[1])

			if err != nil {
				if err.Error() == VALIDATION_ERROR_EMAIL {
					dbServices.SetFieldValue("Errors."+key, val, VALIDATION_ERROR_SPECIFIC_EMAIL)
				}
				isError = true
			}

			if cleanup != "" {
				dbServices.SetFieldValue(key, val, cleanup)
			}

		}

	}

	if isError {
		return errors.New(VALIDATION_ERROR)
	}

	return nil
}

func validateRequired(value string, tagValue string) error {
	if tagValue == "true" {
		if value == "" {
			return errors.New(VALIDATION_ERROR_REQUIRED)
		}
		return nil
	}
	return nil
}

func validateType(value string, tagValue string) (string, error) {
	switch tagValue {
	case dbServices.VALIDATION_TYPE_EMAIL:
		return "", validateEmail(value)
	}
	return "", nil
}

func validateEmail(value string) error {
	if !govalidator.IsEmail(value) {
		return errors.New(VALIDATION_ERROR_EMAIL)
	}
	return nil
}

func getJoins(x reflect.Value, remainingRecursions string) (joins []join) {
	if remainingRecursions == "" {
		return
	}

	fields := strings.Split(remainingRecursions, ".")
	fieldName := fields[0]

	joinsField := x.FieldByName("Joins")

	if joinsField.Kind() != reflect.Struct {
		return
	}

	if fieldName == JOIN_ALL {
		for i := 0; i < joinsField.NumField(); i++ {

			typeField := joinsField.Type().Field(i)
			name := typeField.Name
			tagValue := typeField.Tag.Get("join")
			splitValue := strings.Split(tagValue, ",")
			var j join
			j.collectionName = splitValue[0]
			j.joinSchemaName = splitValue[1]
			j.joinFieldRefName = splitValue[2]
			j.joinFieldName = name
			j.joinSpecified = JOIN_ALL
			joins = append(joins, j)
		}
	} else {
		typeField, ok := joinsField.Type().FieldByName(fieldName)
		if ok == false {
			return
		}
		name := typeField.Name
		tagValue := typeField.Tag.Get("join")
		splitValue := strings.Split(tagValue, ",")
		var j join
		j.collectionName = splitValue[0]
		j.joinSchemaName = splitValue[1]
		j.joinFieldRefName = splitValue[2]
		j.joinFieldName = name
		j.joinSpecified = strings.Replace(remainingRecursions, fieldName+".", "", 1)
		if strings.Contains(j.joinSpecified, "Count") && j.joinSpecified[:5] == "Count" {
			j.joinSpecified = "Count"
		}
		joins = append(joins, j)
	}
	return
}

func IsZeroOfUnderlyingType(x interface{}) bool {
	return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

func Reflect(obj interface{}) []Field {
	var ret []Field
	val := reflect.ValueOf(obj)

	for i := 0; i < val.NumField(); i++ {
		typeField := val.Type().Field(i)
		if typeField.Name != "Errors" && typeField.Name != "Joins" && typeField.Name != "Id" {
			if typeField.Name == "Views" {
				for f := 0; f < val.FieldByName("Views").NumField(); f++ {
					field := Field{}
					field.IsView = true
					name := val.FieldByName("Views").Type().Field(f).Name
					namePart := camelcase.Split(name)
					for x := 0; x < len(namePart); x++ {
						if x > 0 {
							namePart[x] = strings.ToLower(namePart[x])
						}
					}
					field.Name = val.FieldByName("Views").Type().Field(f).Name
					field.Label = strings.Join(namePart[:], " ")
					field.DataType = val.FieldByName("Views").Type().Field(f).Type.Name()
					validate := val.FieldByName("Views").Type().Field(f).Tag.Get("validate")
					if validate != "" {
						//core.Debug.Dump(validate)
						//parts := strings.Split(validate, ",")
						//field.Validation.Required = true//extensions.StringToBool(parts[0])
						//field.Validation.Type = parts[1]
						//field.Validation.Min = parts[2]
						//field.Validation.Max = parts[3]
						//field.Validation.Length = parts[4]
						//field.Validation.LengthMax = parts[4]
						//field.Validation.LengthMin = parts[5]
					}
					ret = append(ret, field)
				}
			} else {
				field := Field{}
				validate := typeField.Tag.Get("validate")
				if validate != "" {
					//core.Debug.Dump(validate)
					//parts := strings.Split(validate, ",")
					//core.Debug.Dump(extensions.StringToBool(parts[0]))
					//field.Validation.Required = extensions.StringToBool(parts[0])
					//field.Validation.Type = parts[1]
					//field.Validation.Min = parts[2]
					//field.Validation.Max = parts[3]
					//field.Validation.Length = parts[4]
					//field.Validation.LengthMax = parts[4]
					//field.Validation.LengthMin = parts[5]
				}
				name := typeField.Name
				namePart := camelcase.Split(name)
				for x := 0; x < len(namePart); x++ {
					if x > 0 {
						namePart[x] = strings.ToLower(namePart[x])
					}
				}
				field.Name = typeField.Name
				field.Label = strings.Join(namePart[:], " ")
				field.DataType = typeField.Type.Name()
				ret = append(ret, field)
			}
		}
	}
	return ret
}

func JoinEntity(collectionQ *Query, y interface{}, j join, id string, fieldToSet reflect.Value, remainingRecursions string, q *Query, endRecursion bool, recursionCount int) (err error) {
	if IsZeroOfUnderlyingType(fieldToSet.Interface()) || j.isMany {

		if j.isMany {
			if remainingRecursions == "Count" {
				cnt, err := collectionQ.ToggleLogFlag(true).Filter(Q(j.joinForeignFieldName, id)).Count()
				if serverSettings.WebConfig.Application.LogJoinQueries {
					collectionQ.LogQuery("JoinEntity() Recursion Count Only")
				}
				if err != nil {
					// err = errCnt
					return err
				}
				countField := fieldToSet.Elem().FieldByName("Count")
				countField.Set(reflect.ValueOf(cnt))
				return err
			}
			err = collectionQ.ToggleLogFlag(true).Filter(Q(j.joinForeignFieldName, id)).All(y)
			if serverSettings.WebConfig.Application.LogJoinQueries {
				collectionQ.LogQuery("JoinEntity({" + j.joinForeignFieldName + ": " + id + "}) Recursion Many")
			}
		} else {
			if j.joinForeignFieldName == "" {
				err = collectionQ.ToggleLogFlag(true).ById(id, y)
				if serverSettings.WebConfig.Application.LogJoinQueries {
					collectionQ.LogQuery("JoinEntity() Recursion Single By Id (" + id + ")")
				}
			} else {
				err = collectionQ.ToggleLogFlag(true).Filter(Q(j.joinForeignFieldName, id)).One(y)
				if serverSettings.WebConfig.Application.LogJoinQueries {
					collectionQ.LogQuery("JoinEntity({" + j.joinForeignFieldName + ": " + id + "}) Recursion Single")
				}
			}
		}

		if err == nil {
			if endRecursion == false && recursionCount > 0 {
				recursionCount--

				in := []reflect.Value{}
				in = append(in, reflect.ValueOf(remainingRecursions))
				in = append(in, reflect.ValueOf(q))
				in = append(in, reflect.ValueOf(recursionCount))

				if j.isMany {

					myArray := reflect.ValueOf(y).Elem()
					for i := 0; i < myArray.Len(); i++ {
						s := myArray.Index(i)
						method := s.Addr().MethodByName("JoinFields")
						values := method.Call(in)
						if values[0].Interface() != nil {
							err = values[0].Interface().(error)
						}
					}
				} else {
					err = CallMethod(y, "JoinFields", in)
				}

			}
			if err == nil {
				if j.isMany {

					itemsField := fieldToSet.Elem().FieldByName("Items")
					countField := fieldToSet.Elem().FieldByName("Count")
					itemsField.Set(reflect.ValueOf(y))
					countField.Set(reflect.ValueOf(reflect.ValueOf(y).Elem().Len()))
				} else {
					fieldToSet.Set(reflect.ValueOf(y))
				}

				if q.renderViews {
					err = q.processViews(y)
					if err != nil {
						return
					}
				}

			}
		}
	} else {
		if endRecursion == false && recursionCount > 0 {
			recursionCount--
			method := fieldToSet.MethodByName("JoinFields")
			in := []reflect.Value{}
			in = append(in, reflect.ValueOf(remainingRecursions))
			in = append(in, reflect.ValueOf(q))
			in = append(in, reflect.ValueOf(recursionCount))
			values := method.Call(in)
			if values[0].Interface() == nil {
				err = nil
				return
			}
			err = values[0].Interface().(error)
		}
	}
	return
}

func CallMethod(i interface{}, methodName string, in []reflect.Value) (err error) {
	var ptr reflect.Value
	var value reflect.Value
	var finalMethod reflect.Value

	value = reflect.ValueOf(i)

	// if we start with a pointer, we need to get value pointed to
	// if we start with a value, we need to get a pointer to that value
	if value.Type().Kind() == reflect.Ptr {
		ptr = value
		value = ptr.Elem()
	} else {
		ptr = reflect.New(reflect.TypeOf(i))
		temp := ptr.Elem()
		temp.Set(value)
	}

	// check for method on value
	method := value.MethodByName(methodName)
	if method.IsValid() {
		finalMethod = method
	}
	// check for method on pointer
	method = ptr.MethodByName(methodName)
	if method.IsValid() {
		finalMethod = method
	}

	if finalMethod.IsValid() {
		values := finalMethod.Call(in)
		if values[0].Interface() == nil {
			err = nil
			return
		}
		err = values[0].Interface().(error)
		return
	}

	// return or panic, method not found of either type
	return nil
}

// Start of autogenerated code....
<|endoftext|>