<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os/exec"
	"path"
	"strings"
	"testing"
	"time"
)

const (
	ANY_DB_DRIVER     = ""
	MULTI_DOMAIN_MODE = ""
	TEST_DEPLOYED_ON  = "localhost:9999"
	TEST_DB           = "goslow_test"
)

var DATA_SOURCE = map[string]string{
	ANY_DB_DRIVER: "", // value doesn't matter
	"sqlite3":     DEFAULT_CONFIG.dataSource,
	"postgres":    "postgres://localhost/" + TEST_DB,
}

type TestCase struct {
	driver                 string
	dataSource             string
	createDefaultEndpoints bool
	adminPathPrefix        string
}

type TestCases []*TestCase

// TODO: do we need *TestCase argument in ServerTest?
type ServerTest func(*testing.T, *httptest.Server, *TestCase)

var (
	multiDomainTestCases = TestCases{
		NewTestCase(ANY_DB_DRIVER, MULTI_DOMAIN_MODE),
	}

	endpointCreationTestCases = TestCases{
		NewTestCase(ANY_DB_DRIVER, MULTI_DOMAIN_MODE),
		NewTestCase(ANY_DB_DRIVER, "/goslow"),
		NewTestCase(ANY_DB_DRIVER, "/goslow/"),
		NewTestCase(ANY_DB_DRIVER, "/te"),
		NewTestCase(ANY_DB_DRIVER, "/te/"),
		NewTestCase(ANY_DB_DRIVER, "/composite/path"),
	}
)

func NewTestCase(driver string, adminPathPrefix string) *TestCase {
	dataSource, knownDriver := DATA_SOURCE[driver]
	if !knownDriver {
		log.Fatalf("unknown driver: <%s>", driver)
	}
	createDefaultEndpoints := (adminPathPrefix == MULTI_DOMAIN_MODE)
	return &TestCase{
		driver:                 driver,
		dataSource:             dataSource,
		createDefaultEndpoints: createDefaultEndpoints,
		adminPathPrefix:        adminPathPrefix,
	}
}

func TestZeroSite(t *testing.T) {
	runAll(t, zeroSiteServerTest, multiDomainTestCases)
}

func runAll(t *testing.T, serverTest ServerTest, testCases TestCases) {
	for _, testCase := range runnable(expandToConcreteDriver(testCases)) {
		run(t, serverTest, testCase)
	}
}

func runnable(testCases TestCases) TestCases {
	runnableTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if testCase.runnable() {
			runnableTestCases = append(runnableTestCases, testCase)
		}
	}
	return runnableTestCases
}

func expandToConcreteDriver(testCases TestCases) TestCases {
	concreteTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if testCase.driver == ANY_DB_DRIVER {
			concreteTestCases = append(concreteTestCases, NewTestCase("sqlite3", testCase.adminPathPrefix))
			concreteTestCases = append(concreteTestCases, NewTestCase("postgres", testCase.adminPathPrefix))
		} else {
			concreteTestCases = append(concreteTestCases, testCase)
		}
	}
	return concreteTestCases
}

func (testCase *TestCase) runnable() bool {
	return !testCase.skippable()
}

func (testCase *TestCase) skippable() bool {
	return testCase.driver == "postgres" && testing.Short()
}

func run(t *testing.T, serverTest ServerTest, testCase *TestCase) {
	if testCase.driver == "postgres" {
		createDb(TEST_DB)
		defer dropDb(TEST_DB)
	}
	goSlowServer := newGoSlowServer(testCase)
	server := httptest.NewServer(goSlowServer)
	defer server.Close()
	defer goSlowServer.storage.db.Close() // so we can drop database
	serverTest(t, server, testCase)
}

func zeroSiteServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	shouldRespondWith(t, DEFAULT_RESPONSE,
		createGET(server.URL, "/", makeFullDomain("0")))
}

func shouldRespondWith(t *testing.T, expectedResponse []byte, request *http.Request) {
	response := do(request)
	bytesShouldBeEqual(t, expectedResponse, read(response))
}

func TestTooLargeDelay(t *testing.T) {
	runAll(t, tooLargeDelayServerTest, multiDomainTestCases)
}

func tooLargeDelayServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	dontAllowToCreateEndpointWithDelay(t, server, time.Duration(1000)*time.Second)
}

func dontAllowToCreateEndpointWithDelay(t *testing.T, server *httptest.Server, delay time.Duration) {
	withNewDomain(server, func(domain string) {
		resp := createEndpoint(server, &Endpoint{Site: getSite(domain), Path: "/", Delay: delay})

		shouldHaveStatusCode(t, http.StatusBadRequest, resp)
		shouldHaveStatusCode(t, http.StatusNotFound, GET(server.URL, "/", domain))
	})
}

type DomainTest func(domain string)

func withNewDomain(server *httptest.Server, domainTest DomainTest) {
	domain := createDomain(server, &Endpoint{Path: "/path-is-irrelevant"})
	domainTest(domain)
}

func TestChangeBuiltinSites(t *testing.T) {
	runAll(t, changeBuiltinSitesServerTest, multiDomainTestCases)
}

func changeBuiltinSitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	dontAllowToChangeSite(t, server, http.StatusForbidden, "0")
	dontAllowToChangeSite(t, server, http.StatusForbidden, "599")
	dontAllowToChangeSite(t, server, http.StatusForbidden, "create")
}

func dontAllowToChangeSite(t *testing.T, server *httptest.Server, expectedStatusCode int, site string) {
	resp := createEndpoint(server, &Endpoint{Site: site, Path: "/test", Response: []byte("hop"), Method: "GET"})
	shouldHaveStatusCode(t, expectedStatusCode, resp)
}

func TestChangeUnknownSites(t *testing.T) {
	runAll(t, changeUnknownSitesServerTest, multiDomainTestCases)
}

func changeUnknownSitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	dontAllowToChangeSite(t, server, http.StatusNotFound, "")
	dontAllowToChangeSite(t, server, http.StatusNotFound, "uknown-site")
	dontAllowToChangeSite(t, server, http.StatusNotFound, "admin-500")
	dontAllowToChangeSite(t, server, http.StatusNotFound, "admin-create")
}

func TestDelaySites(t *testing.T) {
	runAll(t, delaySitesServerTest, multiDomainTestCases)
}

func delaySitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	shouldRespondInTimeInterval(t, 0, 0.1, // seconds
		createGET(server.URL, "/", makeFullDomain("0")),
	)

	shouldRespondInTimeInterval(t, 1, 1.1, // seconds
		createGET(server.URL, "/", makeFullDomain("1")),
	)
}

func TestStatusSites(t *testing.T) {
	runAll(t, statusSitesServerTest, multiDomainTestCases)
}

func statusSitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	siteShouldRespondWith(t, server, 200, "200")
	siteShouldRespondWith(t, server, 404, "404")
	siteShouldRespondWith(t, server, 599, "599")
}

// TODO: do we need to carry server argument in this and similar functions?
func siteShouldRespondWith(t *testing.T, server *httptest.Server, statusCode int, site string) {
	resp := GET(server.URL, "/", makeFullDomain(site))
	shouldHaveStatusCode(t, statusCode, resp)
}

func TestEndpointCreation(t *testing.T) {
	runAll(t, endpointCreationServerTest, endpointCreationTestCases)
}

func endpointCreationServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	prefix := testCase.adminPathPrefix
	isInSingleSiteMode := prefix != ""
	domain, site := TEST_DEPLOYED_ON, ""
	root_response := []byte("haha")
	test_response := []byte("hop")
	test_post_response := []byte("for POST")
	empty_payload := []byte("")

	if isInSingleSiteMode {
		resp := createEndpoint(server, &Endpoint{Path: join(prefix, "/"), Response: root_response})
		shouldHaveStatusCode(t, http.StatusOK, resp)
	} else {
		domain = createDomain(server, &Endpoint{Path: join(prefix, "/"), Response: root_response})
		site = getSite(domain)
	}

	bytesShouldBeEqual(t, read(GET(server.URL, "/", domain)), root_response)

	// testing GET endpoint
	resp := createEndpoint(server, &Endpoint{Site: site, Path: join(prefix, "/test"), Response: test_response, Method: "GET"})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that GET /test endpoint works
	bytesShouldBeEqual(t, read(GET(server.URL, "/test", domain)), test_response)
	// checking that GET /test doesn't affect POST
	resp = POST(server.URL, "/test", domain, []byte(""))
	intsShouldBeEqual(t, 404, resp.StatusCode)

	// testing POST endpoint
	resp = createEndpoint(server, &Endpoint{Site: site, Path: join(prefix, "/test"), Response: test_post_response, Method: "POST",
		Delay: time.Duration(100) * time.Millisecond})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that POST endpoint doesn't affect GET
	bytesShouldBeEqual(t, read(GET(server.URL, "/test", domain)), test_response)
	// checking that POST /test endpoint works
	bytesShouldBeEqual(t, read(POST(server.URL, "/test", domain, empty_payload)), test_post_response)
	shouldRespondInTimeInterval(t, 0.1, 0.15, createPOST(server.URL, "/test", domain, empty_payload))
}

func newGoSlowServer(testCase *TestCase) *Server {
	config := DEFAULT_CONFIG // copies DEFAULT_CONFIG
	config.deployedOn = TEST_DEPLOYED_ON
	config.createDefaultEndpoints = testCase.createDefaultEndpoints
	config.adminPathPrefix = testCase.adminPathPrefix
	config.driver = testCase.driver
	config.dataSource = testCase.dataSource
	return NewServer(&config)
}

// TODO: is it okay to have 3 string arguments?
// TODO: shouldn't host argument come first?
// TODO: same for createGET, POST, and createPOST
func GET(url, path, host string) *http.Response {
	req := createGET(url, path, host)
	return do(req)
}

func do(req *http.Request) *http.Response {
	resp, err := new(http.Client).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	return resp
}

func createGET(url, path, host string) *http.Request {
	return createRequest("GET", url, path, host, nil)
}

// TODO: replace 4 string arguments with maybe template url.URL argument
func createRequest(method, url, path, host string, body io.Reader) *http.Request {
	// TODO: build url with url.URL.String() instead of string concatenation
	req, err := http.NewRequest(method, url+path, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Host = host
	return req
}

func read(resp *http.Response) []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return body
}

func makeFullDomain(site string) string {
	return fmt.Sprintf("%s.%s", site, TEST_DEPLOYED_ON)
}

func bytesShouldBeEqual(t *testing.T, expected, actual []byte) {
	if !bytes.Equal(expected, actual) {
		t.Fatalf("<<%v>> != <<%v>>", string(expected), string(actual))
	}
}

func intsShouldBeEqual(t *testing.T, expected, actual int) {
	if expected != actual {
		t.Fatalf("<<%v>> != <<%v>>", expected, actual)
	}
}

func shouldRespondInTimeInterval(t *testing.T, minSeconds, maxSeconds float64, req *http.Request) {
	start := time.Now()
	resp := do(req)
	read(resp)

	duration := time.Since(start)
	minDuration := secondsToDuration(minSeconds)
	maxDuration := secondsToDuration(maxSeconds)

	if duration < minDuration || duration > maxDuration {
		t.Fatalf("%s%s answered in %v. Not in the interval [%v; %v]",
			req.Host, req.URL.Path, duration, minDuration, maxDuration)
	}
}

// TODO: shouldn't all should* methods accept *http.Request and not *http.Response?
func shouldHaveStatusCode(t *testing.T, statusCode int, resp *http.Response) {
	intsShouldBeEqual(t, statusCode, resp.StatusCode)
}

// TODO: build url with url.URL not Sprintf
// TODO: endpoint is strange argument here, think of something better
func createDomain(server *httptest.Server, endpoint *Endpoint) string {
	resp := POST(server.URL, fmt.Sprintf("%s?output=short&method=GET", endpoint.Path),
		makeFullDomain("create"), endpoint.Response)
	return string(read(resp))
}

func POST(url, path, host string, payload []byte) *http.Response {
	req := createPOST(url, path, host, payload)
	return do(req)
}

func createPOST(url, path, host string, payload []byte) *http.Request {
	return createRequest("POST", url, path, host, bytes.NewReader(payload))
}

func getSite(domain string) string {
	return strings.Split(domain, ".")[0]
}

func createEndpoint(server *httptest.Server, endpoint *Endpoint) *http.Response {
	req := createPOST(server.URL, endpoint.Path, makeFullDomain("admin-"+endpoint.Site),
		endpoint.Response)
	req.URL.RawQuery = getQueryString(endpoint)
	return do(req)
}

func getQueryString(endpoint *Endpoint) string {
	params := url.Values{}
	params.Set("method", endpoint.Method)
	params.Set("delay", fmt.Sprintf("%f", endpoint.Delay.Seconds()))
	return params.Encode()
}

// Wrapper around path.Join. Preserves trailing slash.
func join(elem ...string) string {
	lastElem := elem[len(elem)-1]
	shouldEndWithSlash := strings.HasSuffix(lastElem, "/")
	joined := path.Join(elem...)
	if shouldEndWithSlash {
		return ensureHasSuffix(joined, "/")
	}
	return joined
}

func ensureHasSuffix(s, suffix string) string {
	if !strings.HasSuffix(s, suffix) {
		return s + suffix
	}
	return s
}

func createDb(name string) {
	runCommand("createdb", name)
}

func runCommand(name string, arg ...string) {
	cmd := exec.Command(name, arg...)
	err := cmd.Run()
	if err != nil {
		log.Fatalf("%s error: %s", name, err)
	}
}

func dropDb(name string) {
	runCommand("dropdb", name)
}
<commit_msg>Rename TestStatusSites helper<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os/exec"
	"path"
	"strings"
	"testing"
	"time"
)

const (
	ANY_DB_DRIVER     = ""
	MULTI_DOMAIN_MODE = ""
	TEST_DEPLOYED_ON  = "localhost:9999"
	TEST_DB           = "goslow_test"
)

var DATA_SOURCE = map[string]string{
	ANY_DB_DRIVER: "", // value doesn't matter
	"sqlite3":     DEFAULT_CONFIG.dataSource,
	"postgres":    "postgres://localhost/" + TEST_DB,
}

type TestCase struct {
	driver                 string
	dataSource             string
	createDefaultEndpoints bool
	adminPathPrefix        string
}

type TestCases []*TestCase

// TODO: do we need *TestCase argument in ServerTest?
type ServerTest func(*testing.T, *httptest.Server, *TestCase)

var (
	multiDomainTestCases = TestCases{
		NewTestCase(ANY_DB_DRIVER, MULTI_DOMAIN_MODE),
	}

	endpointCreationTestCases = TestCases{
		NewTestCase(ANY_DB_DRIVER, MULTI_DOMAIN_MODE),
		NewTestCase(ANY_DB_DRIVER, "/goslow"),
		NewTestCase(ANY_DB_DRIVER, "/goslow/"),
		NewTestCase(ANY_DB_DRIVER, "/te"),
		NewTestCase(ANY_DB_DRIVER, "/te/"),
		NewTestCase(ANY_DB_DRIVER, "/composite/path"),
	}
)

func NewTestCase(driver string, adminPathPrefix string) *TestCase {
	dataSource, knownDriver := DATA_SOURCE[driver]
	if !knownDriver {
		log.Fatalf("unknown driver: <%s>", driver)
	}
	createDefaultEndpoints := (adminPathPrefix == MULTI_DOMAIN_MODE)
	return &TestCase{
		driver:                 driver,
		dataSource:             dataSource,
		createDefaultEndpoints: createDefaultEndpoints,
		adminPathPrefix:        adminPathPrefix,
	}
}

func TestZeroSite(t *testing.T) {
	runAll(t, zeroSiteServerTest, multiDomainTestCases)
}

func runAll(t *testing.T, serverTest ServerTest, testCases TestCases) {
	for _, testCase := range runnable(expandToConcreteDriver(testCases)) {
		run(t, serverTest, testCase)
	}
}

func runnable(testCases TestCases) TestCases {
	runnableTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if testCase.runnable() {
			runnableTestCases = append(runnableTestCases, testCase)
		}
	}
	return runnableTestCases
}

func expandToConcreteDriver(testCases TestCases) TestCases {
	concreteTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if testCase.driver == ANY_DB_DRIVER {
			concreteTestCases = append(concreteTestCases, NewTestCase("sqlite3", testCase.adminPathPrefix))
			concreteTestCases = append(concreteTestCases, NewTestCase("postgres", testCase.adminPathPrefix))
		} else {
			concreteTestCases = append(concreteTestCases, testCase)
		}
	}
	return concreteTestCases
}

func (testCase *TestCase) runnable() bool {
	return !testCase.skippable()
}

func (testCase *TestCase) skippable() bool {
	return testCase.driver == "postgres" && testing.Short()
}

func run(t *testing.T, serverTest ServerTest, testCase *TestCase) {
	if testCase.driver == "postgres" {
		createDb(TEST_DB)
		defer dropDb(TEST_DB)
	}
	goSlowServer := newGoSlowServer(testCase)
	server := httptest.NewServer(goSlowServer)
	defer server.Close()
	defer goSlowServer.storage.db.Close() // so we can drop database
	serverTest(t, server, testCase)
}

func zeroSiteServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	shouldRespondWith(t, DEFAULT_RESPONSE,
		createGET(server.URL, "/", makeFullDomain("0")))
}

func shouldRespondWith(t *testing.T, expectedResponse []byte, request *http.Request) {
	response := do(request)
	bytesShouldBeEqual(t, expectedResponse, read(response))
}

func TestTooLargeDelay(t *testing.T) {
	runAll(t, tooLargeDelayServerTest, multiDomainTestCases)
}

func tooLargeDelayServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	dontAllowToCreateEndpointWithDelay(t, server, time.Duration(1000)*time.Second)
}

func dontAllowToCreateEndpointWithDelay(t *testing.T, server *httptest.Server, delay time.Duration) {
	withNewDomain(server, func(domain string) {
		resp := createEndpoint(server, &Endpoint{Site: getSite(domain), Path: "/", Delay: delay})

		shouldHaveStatusCode(t, http.StatusBadRequest, resp)
		shouldHaveStatusCode(t, http.StatusNotFound, GET(server.URL, "/", domain))
	})
}

type DomainTest func(domain string)

func withNewDomain(server *httptest.Server, domainTest DomainTest) {
	domain := createDomain(server, &Endpoint{Path: "/path-is-irrelevant"})
	domainTest(domain)
}

func TestChangeBuiltinSites(t *testing.T) {
	runAll(t, changeBuiltinSitesServerTest, multiDomainTestCases)
}

func changeBuiltinSitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	dontAllowToChangeSite(t, server, http.StatusForbidden, "0")
	dontAllowToChangeSite(t, server, http.StatusForbidden, "599")
	dontAllowToChangeSite(t, server, http.StatusForbidden, "create")
}

func dontAllowToChangeSite(t *testing.T, server *httptest.Server, expectedStatusCode int, site string) {
	resp := createEndpoint(server, &Endpoint{Site: site, Path: "/test", Response: []byte("hop"), Method: "GET"})
	shouldHaveStatusCode(t, expectedStatusCode, resp)
}

func TestChangeUnknownSites(t *testing.T) {
	runAll(t, changeUnknownSitesServerTest, multiDomainTestCases)
}

func changeUnknownSitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	dontAllowToChangeSite(t, server, http.StatusNotFound, "")
	dontAllowToChangeSite(t, server, http.StatusNotFound, "uknown-site")
	dontAllowToChangeSite(t, server, http.StatusNotFound, "admin-500")
	dontAllowToChangeSite(t, server, http.StatusNotFound, "admin-create")
}

func TestDelaySites(t *testing.T) {
	runAll(t, delaySitesServerTest, multiDomainTestCases)
}

func delaySitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	shouldRespondInTimeInterval(t, 0, 0.1, // seconds
		createGET(server.URL, "/", makeFullDomain("0")),
	)

	shouldRespondInTimeInterval(t, 1, 1.1, // seconds
		createGET(server.URL, "/", makeFullDomain("1")),
	)
}

func TestStatusSites(t *testing.T) {
	runAll(t, statusSitesServerTest, multiDomainTestCases)
}

func statusSitesServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	siteShouldRespondWithStatusCode(t, server, 200, "200")
	siteShouldRespondWithStatusCode(t, server, 404, "404")
	siteShouldRespondWithStatusCode(t, server, 599, "599")
}

// TODO: do we need to carry server argument in this and similar functions?
func siteShouldRespondWithStatusCode(t *testing.T, server *httptest.Server, statusCode int, site string) {
	resp := GET(server.URL, "/", makeFullDomain(site))
	shouldHaveStatusCode(t, statusCode, resp)
}

func TestEndpointCreation(t *testing.T) {
	runAll(t, endpointCreationServerTest, endpointCreationTestCases)
}

func endpointCreationServerTest(t *testing.T, server *httptest.Server, testCase *TestCase) {
	prefix := testCase.adminPathPrefix
	isInSingleSiteMode := prefix != ""
	domain, site := TEST_DEPLOYED_ON, ""
	root_response := []byte("haha")
	test_response := []byte("hop")
	test_post_response := []byte("for POST")
	empty_payload := []byte("")

	if isInSingleSiteMode {
		resp := createEndpoint(server, &Endpoint{Path: join(prefix, "/"), Response: root_response})
		shouldHaveStatusCode(t, http.StatusOK, resp)
	} else {
		domain = createDomain(server, &Endpoint{Path: join(prefix, "/"), Response: root_response})
		site = getSite(domain)
	}

	bytesShouldBeEqual(t, read(GET(server.URL, "/", domain)), root_response)

	// testing GET endpoint
	resp := createEndpoint(server, &Endpoint{Site: site, Path: join(prefix, "/test"), Response: test_response, Method: "GET"})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that GET /test endpoint works
	bytesShouldBeEqual(t, read(GET(server.URL, "/test", domain)), test_response)
	// checking that GET /test doesn't affect POST
	resp = POST(server.URL, "/test", domain, []byte(""))
	intsShouldBeEqual(t, 404, resp.StatusCode)

	// testing POST endpoint
	resp = createEndpoint(server, &Endpoint{Site: site, Path: join(prefix, "/test"), Response: test_post_response, Method: "POST",
		Delay: time.Duration(100) * time.Millisecond})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that POST endpoint doesn't affect GET
	bytesShouldBeEqual(t, read(GET(server.URL, "/test", domain)), test_response)
	// checking that POST /test endpoint works
	bytesShouldBeEqual(t, read(POST(server.URL, "/test", domain, empty_payload)), test_post_response)
	shouldRespondInTimeInterval(t, 0.1, 0.15, createPOST(server.URL, "/test", domain, empty_payload))
}

func newGoSlowServer(testCase *TestCase) *Server {
	config := DEFAULT_CONFIG // copies DEFAULT_CONFIG
	config.deployedOn = TEST_DEPLOYED_ON
	config.createDefaultEndpoints = testCase.createDefaultEndpoints
	config.adminPathPrefix = testCase.adminPathPrefix
	config.driver = testCase.driver
	config.dataSource = testCase.dataSource
	return NewServer(&config)
}

// TODO: is it okay to have 3 string arguments?
// TODO: shouldn't host argument come first?
// TODO: same for createGET, POST, and createPOST
func GET(url, path, host string) *http.Response {
	req := createGET(url, path, host)
	return do(req)
}

func do(req *http.Request) *http.Response {
	resp, err := new(http.Client).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	return resp
}

func createGET(url, path, host string) *http.Request {
	return createRequest("GET", url, path, host, nil)
}

// TODO: replace 4 string arguments with maybe template url.URL argument
func createRequest(method, url, path, host string, body io.Reader) *http.Request {
	// TODO: build url with url.URL.String() instead of string concatenation
	req, err := http.NewRequest(method, url+path, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Host = host
	return req
}

func read(resp *http.Response) []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return body
}

func makeFullDomain(site string) string {
	return fmt.Sprintf("%s.%s", site, TEST_DEPLOYED_ON)
}

func bytesShouldBeEqual(t *testing.T, expected, actual []byte) {
	if !bytes.Equal(expected, actual) {
		t.Fatalf("<<%v>> != <<%v>>", string(expected), string(actual))
	}
}

func intsShouldBeEqual(t *testing.T, expected, actual int) {
	if expected != actual {
		t.Fatalf("<<%v>> != <<%v>>", expected, actual)
	}
}

func shouldRespondInTimeInterval(t *testing.T, minSeconds, maxSeconds float64, req *http.Request) {
	start := time.Now()
	resp := do(req)
	read(resp)

	duration := time.Since(start)
	minDuration := secondsToDuration(minSeconds)
	maxDuration := secondsToDuration(maxSeconds)

	if duration < minDuration || duration > maxDuration {
		t.Fatalf("%s%s answered in %v. Not in the interval [%v; %v]",
			req.Host, req.URL.Path, duration, minDuration, maxDuration)
	}
}

// TODO: shouldn't all should* methods accept *http.Request and not *http.Response?
func shouldHaveStatusCode(t *testing.T, statusCode int, resp *http.Response) {
	intsShouldBeEqual(t, statusCode, resp.StatusCode)
}

// TODO: build url with url.URL not Sprintf
// TODO: endpoint is strange argument here, think of something better
func createDomain(server *httptest.Server, endpoint *Endpoint) string {
	resp := POST(server.URL, fmt.Sprintf("%s?output=short&method=GET", endpoint.Path),
		makeFullDomain("create"), endpoint.Response)
	return string(read(resp))
}

func POST(url, path, host string, payload []byte) *http.Response {
	req := createPOST(url, path, host, payload)
	return do(req)
}

func createPOST(url, path, host string, payload []byte) *http.Request {
	return createRequest("POST", url, path, host, bytes.NewReader(payload))
}

func getSite(domain string) string {
	return strings.Split(domain, ".")[0]
}

func createEndpoint(server *httptest.Server, endpoint *Endpoint) *http.Response {
	req := createPOST(server.URL, endpoint.Path, makeFullDomain("admin-"+endpoint.Site),
		endpoint.Response)
	req.URL.RawQuery = getQueryString(endpoint)
	return do(req)
}

func getQueryString(endpoint *Endpoint) string {
	params := url.Values{}
	params.Set("method", endpoint.Method)
	params.Set("delay", fmt.Sprintf("%f", endpoint.Delay.Seconds()))
	return params.Encode()
}

// Wrapper around path.Join. Preserves trailing slash.
func join(elem ...string) string {
	lastElem := elem[len(elem)-1]
	shouldEndWithSlash := strings.HasSuffix(lastElem, "/")
	joined := path.Join(elem...)
	if shouldEndWithSlash {
		return ensureHasSuffix(joined, "/")
	}
	return joined
}

func ensureHasSuffix(s, suffix string) string {
	if !strings.HasSuffix(s, suffix) {
		return s + suffix
	}
	return s
}

func createDb(name string) {
	runCommand("createdb", name)
}

func runCommand(name string, arg ...string) {
	cmd := exec.Command(name, arg...)
	err := cmd.Run()
	if err != nil {
		log.Fatalf("%s error: %s", name, err)
	}
}

func dropDb(name string) {
	runCommand("dropdb", name)
}
<|endoftext|>


<commit_before>package exec

import (
	"encoding/base64"
	"io"
	"net/url"

	log "github.com/Sirupsen/logrus"

	"github.com/rancher/websocket-proxy/backend"
	"github.com/rancher/websocket-proxy/common"

	"github.com/docker/distribution/context"
	"github.com/docker/docker/api/types"
	"github.com/rancher/agent/service/hostapi/auth"
	"github.com/rancher/agent/service/hostapi/events"
	"runtime"
)

type Handler struct {
}

func (h *Handler) Handle(key string, initialMessage string, incomingMessages <-chan string, response chan<- common.Message) {
	defer backend.SignalHandlerClosed(key, response)

	requestURL, err := url.Parse(initialMessage)
	if err != nil {
		log.WithFields(log.Fields{"error": err, "url": initialMessage}).Error("Couldn't parse url.")
		return
	}
	tokenString := requestURL.Query().Get("token")
	token, valid := auth.GetAndCheckToken(tokenString)
	if !valid {
		return
	}

	execMap := token.Claims["exec"].(map[string]interface{})
	execConfig, id := convert(execMap)

	client, err := events.NewDockerClient()
	if err != nil {
		log.WithFields(log.Fields{"error": err}).Error("Couldn't get docker client.")
		return
	}

	execObj, err := client.ContainerExecCreate(context.Background(), id, execConfig)
	if err != nil {
		return
	}
	hijackResp, err := client.ContainerExecAttach(context.Background(), execObj.ID, execConfig)
	if err != nil {
		return
	}

	go func(w io.WriteCloser) {
		for {
			msg, ok := <-incomingMessages
			if !ok {
				if _, err := w.Write([]byte("\x04")); err != nil {
					log.WithFields(log.Fields{"error": err}).Error("Error writing EOT message.")
				}
				w.Close()
				return
			}
			data, err := base64.StdEncoding.DecodeString(msg)
			if err != nil {
				log.WithFields(log.Fields{"error": err}).Error("Error decoding message.")
				continue
			}
			w.Write([]byte(data))
		}
	}(hijackResp.Conn)

	go func(r io.Reader) {
		buffer := make([]byte, 4096, 4096)
		for {
			c, err := r.Read(buffer)
			if c > 0 {
				text := base64.StdEncoding.EncodeToString(buffer[:c])
				message := common.Message{
					Key:  key,
					Type: common.Body,
					Body: text,
				}
				response <- message
			}
			if err != nil {
				break
			}
		}
	}(hijackResp.Reader)

	select {}
}

func convert(execMap map[string]interface{}) (types.ExecConfig, string) {
	// Not fancy at all
	config := types.ExecConfig{}
	containerID := ""

	if param, ok := execMap["AttachStdin"]; ok {
		if val, ok := param.(bool); ok {
			config.AttachStdin = val
		}
	}

	if param, ok := execMap["AttachStdout"]; ok {
		if val, ok := param.(bool); ok {
			config.AttachStdout = val
		}
	}

	if param, ok := execMap["AttachStderr"]; ok {
		if val, ok := param.(bool); ok {
			config.AttachStderr = val
		}
	}

	if param, ok := execMap["Tty"]; ok {
		if val, ok := param.(bool); ok {
			config.Tty = val
		}
	}

	if param, ok := execMap["Container"]; ok {
		if val, ok := param.(string); ok {
			containerID = val
		}
	}

	if param, ok := execMap["Cmd"]; ok {
		cmd := []string{}
		if list, ok := param.([]interface{}); ok {
			for _, item := range list {
				if val, ok := item.(string); ok {
					cmd = append(cmd, val)
				}
			}
		}
		config.Cmd = cmd
	}

	if runtime.GOOS == "windows" {
		config.Cmd = []string{"powershell"}
	}

	return config, containerID
}
<commit_msg>Don't use go routine for exec reader<commit_after>package exec

import (
	"encoding/base64"
	"io"
	"net/url"

	log "github.com/Sirupsen/logrus"

	"github.com/rancher/websocket-proxy/backend"
	"github.com/rancher/websocket-proxy/common"

	"github.com/docker/distribution/context"
	"github.com/docker/docker/api/types"
	"github.com/rancher/agent/service/hostapi/auth"
	"github.com/rancher/agent/service/hostapi/events"
	"runtime"
)

type Handler struct {
}

func (h *Handler) Handle(key string, initialMessage string, incomingMessages <-chan string, response chan<- common.Message) {
	defer backend.SignalHandlerClosed(key, response)

	requestURL, err := url.Parse(initialMessage)
	if err != nil {
		log.WithFields(log.Fields{"error": err, "url": initialMessage}).Error("Couldn't parse url.")
		return
	}
	tokenString := requestURL.Query().Get("token")
	token, valid := auth.GetAndCheckToken(tokenString)
	if !valid {
		return
	}

	execMap := token.Claims["exec"].(map[string]interface{})
	execConfig, id := convert(execMap)

	client, err := events.NewDockerClient()
	if err != nil {
		log.WithFields(log.Fields{"error": err}).Error("Couldn't get docker client.")
		return
	}

	execObj, err := client.ContainerExecCreate(context.Background(), id, execConfig)
	if err != nil {
		return
	}
	hijackResp, err := client.ContainerExecAttach(context.Background(), execObj.ID, execConfig)
	if err != nil {
		return
	}

	go func(w io.WriteCloser) {
		for {
			msg, ok := <-incomingMessages
			if !ok {
				if _, err := w.Write([]byte("\x04")); err != nil {
					log.WithFields(log.Fields{"error": err}).Error("Error writing EOT message.")
				}
				w.Close()
				return
			}
			data, err := base64.StdEncoding.DecodeString(msg)
			if err != nil {
				log.WithFields(log.Fields{"error": err}).Error("Error decoding message.")
				continue
			}
			w.Write([]byte(data))
		}
	}(hijackResp.Conn)

	buffer := make([]byte, 4096, 4096)
	for {
		c, err := hijackResp.Reader.Read(buffer)
		if c > 0 {
			text := base64.StdEncoding.EncodeToString(buffer[:c])
			message := common.Message{
				Key:  key,
				Type: common.Body,
				Body: text,
			}
			response <- message
		}
		if err != nil {
			break
		}
	}
}

func convert(execMap map[string]interface{}) (types.ExecConfig, string) {
	// Not fancy at all
	config := types.ExecConfig{}
	containerID := ""

	if param, ok := execMap["AttachStdin"]; ok {
		if val, ok := param.(bool); ok {
			config.AttachStdin = val
		}
	}

	if param, ok := execMap["AttachStdout"]; ok {
		if val, ok := param.(bool); ok {
			config.AttachStdout = val
		}
	}

	if param, ok := execMap["AttachStderr"]; ok {
		if val, ok := param.(bool); ok {
			config.AttachStderr = val
		}
	}

	if param, ok := execMap["Tty"]; ok {
		if val, ok := param.(bool); ok {
			config.Tty = val
		}
	}

	if param, ok := execMap["Container"]; ok {
		if val, ok := param.(string); ok {
			containerID = val
		}
	}

	if param, ok := execMap["Cmd"]; ok {
		cmd := []string{}
		if list, ok := param.([]interface{}); ok {
			for _, item := range list {
				if val, ok := item.(string); ok {
					cmd = append(cmd, val)
				}
			}
		}
		config.Cmd = cmd
	}

	if runtime.GOOS == "windows" {
		config.Cmd = []string{"powershell"}
	}

	return config, containerID
}
<|endoftext|>


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

package email

import (
	"bytes"
	"io/ioutil"
	"log"
	"net/mail"
	"reflect"
	"testing"
	"text/template"

	gomail "gopkg.in/gomail.v2"
)

func TestPathMatch(t *testing.T) {
	tests := []struct {
		pattern, path string
		match         bool //want
	}{
		// Valid matches
		{"/private", "/private", true},
		{"/private/", "/private/a/b", true},
		{"domain.tld:8080/private/", "domain.tld:8080/private/a/b", true},
		{"/", "/private/a/b", true},

		// Invalid matches
		{"/private/", "/private", false},
		{"/private", "/privat", false},
		{"/", "", false},
		{"domain.tld:8080/private", "domain.tld:8080/private/a/b", false},
		{"private", "/private", false},
		{"/private", "/private/", false},
		{"", "/private", false},
	}

	for i, test := range tests {
		want, got := test.match, MatchResource(test.pattern, test.path)
		if want != got {
			t.Errorf("test #%d; want %t, got %t", i, want, got)
		}
	}
}

func TestAddPattern(t *testing.T) {
	s := &UserService{}
	if err := s.AddResource("/private"); err != nil {
		t.Error(err)
	}
	if err := s.AddResource(""); err == nil {
		t.Error("expect error, got nil")
	}
}

func TestListed(t *testing.T) {
	s := &UserService{}
	if actual, expect := s.Listed("leeloo"), false; actual != expect {
		t.Errorf("expect %t, got %t", expect, actual)
	}
	if actual, expect := s.Listed("leeloo@dallas"), false; actual != expect {
		t.Errorf("expect %t, got %t", expect, actual)
	}
}

func TestAddHandle(t *testing.T) {
	s := &UserService{}
	if err := s.AddHandle("leeloo@dallas"); err != nil {
		t.Error(err)
	}
	if err := s.AddHandle("leeloo"); err == nil {
		t.Errorf("expect AddHandle to error, but did not")
	}
}

func TestAuthorized(t *testing.T) {
	s := &UserService{}
	pattern, handle, rawurl := "/private/", "leeloo@dallas", "/private/a"
	if err := s.AddHandle(handle); err != nil {
		t.Error(err)
	}
	if err := s.AddResource(pattern); err != nil {
		t.Error(err)
	}
	if actual, expect := s.Authorized(handle, "GET", rawurl), true; actual != expect {
		t.Errorf("want %t, got %t", expect, actual)
	}
	if actual, expect := s.Authorized("korben@dallas", "GET", rawurl), false; actual != expect {
		t.Errorf("want %t, got %t", expect, actual)
	}
	if actual, expect := s.Authorized("anonymous", "GET", "/public"), true; actual != expect {
		t.Errorf("want %t, got %t", expect, actual)
	}
}

func TestNotify(t *testing.T) {
	s := &UserService{}
	s.template = template.Must(template.New("").Parse(msgTmpl))
	s.channel = make(chan *gomail.Message)

	from, err := mail.ParseAddress("Multipass Bot <no-reply@dallas>")
	if err != nil {
		t.Error(err)
	}
	s.from = from

	handle := "leeloo@dallas"
	loginurl := "http://example.com/multipass/login?token="
	if err := s.Notify("leeloo", loginurl); err == nil {
		t.Error("expect Notify to error, but did not")
	}
	go func() {
		if err := s.Notify(handle, loginurl); err != nil {
			t.Error(err)
		}
	}()

	buf := new(bytes.Buffer)
	message, ok := <-s.channel
	if !ok {
		t.Error("oops")
	}
	n, err := message.WriteTo(buf)
	if err != nil {
		t.Error(err)
	}
	if n < 1 {
		t.Errorf("want n > 0, got %d", n)
	}
	if err := s.Close(); err != nil {
		t.Error(err)
	}
}

func TestSplitLocalDomain(t *testing.T) {
	tests := []struct {
		address, local, domain string
		shouldErr              bool
	}{
		// Valid addresses
		{"bob@example.com", "bob", "example.com", false},
		{"bob.smith@example.com", "bob.smith", "example.com", false},
		{"bob@example", "bob", "example", false},
		{"b@c", "b", "c", false},
		{"bob+plus@example.com", "bob+plus", "example.com", false},

		// Invalid addresses
		{"@example.com", "", "", true},
		{"bob@@example.com", "", "", true},
		{"bob@bob@example.com", "", "", true},
		{"bob@", "", "", true},
		{"@", "", "", true},
		{"", "", "", true},
	}
	for i, test := range tests {
		local, domain, err := SplitLocalDomain(test.address)
		if err != nil {
			if test.shouldErr {
				continue
			}
			t.Errorf("test #%d returned unexpected error %s", i, err)
			continue
		}
		if got, want := local, test.local; got != want {
			t.Errorf("test #%d; want local %s, got %s", i, want, got)
		}
		if got, want := domain, test.domain; got != want {
			t.Errorf("test #%d; want domain %s, got %s", i, want, got)
		}
	}
}

func TestMatchPattern(t *testing.T) {
	tests := []struct {
		pattern, address string
		shouldMatch      bool
	}{
		// Valid matches
		{"@", "bob@example.com", true},
		{"@example.com", "bob@example.com", true},
		{"bob@example.com", "bob@example.com", true},
		{"bob.smith@example.com", "bob.smith@example.com", true},
		{"bob+plus@example.com", "bob+plus@example.com", true},

		// Invalid matches
		{"@", "bob", false},
		{"@", "bob@", false},
		{"@", "@example", false},
		{"", "bob@example.com", false},
		{"bob", "bob@example.com", false},
		{"example.com", "bob@example.com", false},
		{"@example.org", "bob@example.com", false},
		{"bob@example.org", "ben@example.org", false},
		{"bob@example", "bob@example.com", false},
	}
	for i, test := range tests {
		got, want := MatchHandle(test.pattern, test.address), test.shouldMatch
		if got != want {
			t.Errorf("test #%d; want pattern %s to match address %s, but failed",
				i, test.pattern, test.address)
		}
	}

}

func TestValidHandle(t *testing.T) {
	tests := []struct {
		handle         string
		shouldValidate bool
	}{
		// Valid
		{"@", true},
		{"@example.com", true},
		{"bob@example.com", true},
		{"bob.smith@example.com", true},
		{"bob+plus@example.com", true},

		// Invalid
		{"", false},
		{"bob", false},
		{"example.com", false},
		{"bob@", false},
	}
	for i, test := range tests {
		got, want := ValidHandle(test.handle), test.shouldValidate
		if got != want {
			t.Errorf("test #%d; want ValidHandle(\"%s\") to return %t, got %t",
				i, test.handle, want, got)
		}
	}

}

func TestSendmail(t *testing.T) {
	m := gomail.NewMessage()
	m.SetHeader("From", "leeloo@dallas")
	m.SetHeader("To", "korben@dallas")
	m.SetBody("text/plain", "Hello!")

	output, err := sendmail(m, "cat")
	if err != nil {
		t.Errorf("cat: %v", err)
	}

	// Need to parse messages before comparison as the order of headers may
	// differ

	mGot, err := mail.ReadMessage(bytes.NewBuffer(output))
	if err != nil {
		log.Fatal(err)
	}

	buf := new(bytes.Buffer)
	if _, err := m.WriteTo(buf); err != nil {
		t.Error(err)
	}
	mWant, err := mail.ReadMessage(buf)
	if err != nil {
		t.Fatal(err)
	}

	if want, got := mWant.Header, mGot.Header; !reflect.DeepEqual(want, got) {
		t.Errorf("cat: want %q, got %q", want, got)
	}

	bGot, err := ioutil.ReadAll(mGot.Body)
	bWant, err := ioutil.ReadAll(mWant.Body)
	if want, got := bWant, bGot; !bytes.Equal(want, got) {
		t.Errorf("cat: want %q, got %q", want, got)
	}
}
<commit_msg>Refactor TestSendmail cleanup ineffectual assignments<commit_after>// Copyright 2016 Lars Wiegman. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.

package email

import (
	"bytes"
	"io/ioutil"
	"log"
	"net/mail"
	"reflect"
	"testing"
	"text/template"

	gomail "gopkg.in/gomail.v2"
)

func TestPathMatch(t *testing.T) {
	tests := []struct {
		pattern, path string
		match         bool //want
	}{
		// Valid matches
		{"/private", "/private", true},
		{"/private/", "/private/a/b", true},
		{"domain.tld:8080/private/", "domain.tld:8080/private/a/b", true},
		{"/", "/private/a/b", true},

		// Invalid matches
		{"/private/", "/private", false},
		{"/private", "/privat", false},
		{"/", "", false},
		{"domain.tld:8080/private", "domain.tld:8080/private/a/b", false},
		{"private", "/private", false},
		{"/private", "/private/", false},
		{"", "/private", false},
	}

	for i, test := range tests {
		want, got := test.match, MatchResource(test.pattern, test.path)
		if want != got {
			t.Errorf("test #%d; want %t, got %t", i, want, got)
		}
	}
}

func TestAddPattern(t *testing.T) {
	s := &UserService{}
	if err := s.AddResource("/private"); err != nil {
		t.Error(err)
	}
	if err := s.AddResource(""); err == nil {
		t.Error("expect error, got nil")
	}
}

func TestListed(t *testing.T) {
	s := &UserService{}
	if actual, expect := s.Listed("leeloo"), false; actual != expect {
		t.Errorf("expect %t, got %t", expect, actual)
	}
	if actual, expect := s.Listed("leeloo@dallas"), false; actual != expect {
		t.Errorf("expect %t, got %t", expect, actual)
	}
}

func TestAddHandle(t *testing.T) {
	s := &UserService{}
	if err := s.AddHandle("leeloo@dallas"); err != nil {
		t.Error(err)
	}
	if err := s.AddHandle("leeloo"); err == nil {
		t.Errorf("expect AddHandle to error, but did not")
	}
}

func TestAuthorized(t *testing.T) {
	s := &UserService{}
	pattern, handle, rawurl := "/private/", "leeloo@dallas", "/private/a"
	if err := s.AddHandle(handle); err != nil {
		t.Error(err)
	}
	if err := s.AddResource(pattern); err != nil {
		t.Error(err)
	}
	if actual, expect := s.Authorized(handle, "GET", rawurl), true; actual != expect {
		t.Errorf("want %t, got %t", expect, actual)
	}
	if actual, expect := s.Authorized("korben@dallas", "GET", rawurl), false; actual != expect {
		t.Errorf("want %t, got %t", expect, actual)
	}
	if actual, expect := s.Authorized("anonymous", "GET", "/public"), true; actual != expect {
		t.Errorf("want %t, got %t", expect, actual)
	}
}

func TestNotify(t *testing.T) {
	s := &UserService{}
	s.template = template.Must(template.New("").Parse(msgTmpl))
	s.channel = make(chan *gomail.Message)

	from, err := mail.ParseAddress("Multipass Bot <no-reply@dallas>")
	if err != nil {
		t.Error(err)
	}
	s.from = from

	handle := "leeloo@dallas"
	loginurl := "http://example.com/multipass/login?token="
	if err := s.Notify("leeloo", loginurl); err == nil {
		t.Error("expect Notify to error, but did not")
	}
	go func() {
		if err := s.Notify(handle, loginurl); err != nil {
			t.Error(err)
		}
	}()

	buf := new(bytes.Buffer)
	message, ok := <-s.channel
	if !ok {
		t.Error("oops")
	}
	n, err := message.WriteTo(buf)
	if err != nil {
		t.Error(err)
	}
	if n < 1 {
		t.Errorf("want n > 0, got %d", n)
	}
	if err := s.Close(); err != nil {
		t.Error(err)
	}
}

func TestSplitLocalDomain(t *testing.T) {
	tests := []struct {
		address, local, domain string
		shouldErr              bool
	}{
		// Valid addresses
		{"bob@example.com", "bob", "example.com", false},
		{"bob.smith@example.com", "bob.smith", "example.com", false},
		{"bob@example", "bob", "example", false},
		{"b@c", "b", "c", false},
		{"bob+plus@example.com", "bob+plus", "example.com", false},

		// Invalid addresses
		{"@example.com", "", "", true},
		{"bob@@example.com", "", "", true},
		{"bob@bob@example.com", "", "", true},
		{"bob@", "", "", true},
		{"@", "", "", true},
		{"", "", "", true},
	}
	for i, test := range tests {
		local, domain, err := SplitLocalDomain(test.address)
		if err != nil {
			if test.shouldErr {
				continue
			}
			t.Errorf("test #%d returned unexpected error %s", i, err)
			continue
		}
		if got, want := local, test.local; got != want {
			t.Errorf("test #%d; want local %s, got %s", i, want, got)
		}
		if got, want := domain, test.domain; got != want {
			t.Errorf("test #%d; want domain %s, got %s", i, want, got)
		}
	}
}

func TestMatchPattern(t *testing.T) {
	tests := []struct {
		pattern, address string
		shouldMatch      bool
	}{
		// Valid matches
		{"@", "bob@example.com", true},
		{"@example.com", "bob@example.com", true},
		{"bob@example.com", "bob@example.com", true},
		{"bob.smith@example.com", "bob.smith@example.com", true},
		{"bob+plus@example.com", "bob+plus@example.com", true},

		// Invalid matches
		{"@", "bob", false},
		{"@", "bob@", false},
		{"@", "@example", false},
		{"", "bob@example.com", false},
		{"bob", "bob@example.com", false},
		{"example.com", "bob@example.com", false},
		{"@example.org", "bob@example.com", false},
		{"bob@example.org", "ben@example.org", false},
		{"bob@example", "bob@example.com", false},
	}
	for i, test := range tests {
		got, want := MatchHandle(test.pattern, test.address), test.shouldMatch
		if got != want {
			t.Errorf("test #%d; want pattern %s to match address %s, but failed",
				i, test.pattern, test.address)
		}
	}

}

func TestValidHandle(t *testing.T) {
	tests := []struct {
		handle         string
		shouldValidate bool
	}{
		// Valid
		{"@", true},
		{"@example.com", true},
		{"bob@example.com", true},
		{"bob.smith@example.com", true},
		{"bob+plus@example.com", true},

		// Invalid
		{"", false},
		{"bob", false},
		{"example.com", false},
		{"bob@", false},
	}
	for i, test := range tests {
		got, want := ValidHandle(test.handle), test.shouldValidate
		if got != want {
			t.Errorf("test #%d; want ValidHandle(\"%s\") to return %t, got %t",
				i, test.handle, want, got)
		}
	}

}

func TestSendmail(t *testing.T) {
	m := gomail.NewMessage()
	m.SetHeader("From", "leeloo@dallas")
	m.SetHeader("To", "korben@dallas")
	m.SetBody("text/plain", "Hello!")

	output, err := sendmail(m, "cat")
	if err != nil {
		t.Errorf("cat: %v", err)
	}

	// Need to parse messages before comparison as the order of headers may
	// differ

	buf := bytes.NewBuffer(output)
	mGot, err := mail.ReadMessage(buf)
	if err != nil {
		log.Fatal(err)
	}
	buf.Reset()

	if _, err := m.WriteTo(buf); err != nil {
		t.Error(err)
	}
	mWant, err := mail.ReadMessage(buf)
	if err != nil {
		t.Fatal(err)
	}

	// validate Header
	if want, got := mWant.Header, mGot.Header; !reflect.DeepEqual(want, got) {
		t.Errorf("cat: want %q, got %q", want, got)
	}

	// validate Body
	bGot, err := ioutil.ReadAll(mGot.Body)
	if err != nil {
		t.Fatal(err)
	}
	bWant, err := ioutil.ReadAll(mWant.Body)
	if err != nil {
		t.Fatal(err)
	}
	if want, got := bWant, bGot; !bytes.Equal(want, got) {
		t.Errorf("cat: want %q, got %q", want, got)
	}
}
<|endoftext|>


<commit_before>package shared

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/lxc/lxd/shared/logger"
)

type Jmap map[string]interface{}

func (m Jmap) GetString(key string) (string, error) {
	if val, ok := m[key]; !ok {
		return "", fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(string); !ok {
		return "", fmt.Errorf("`%s` was not a string", key)
	} else {
		return val, nil
	}
}

func (m Jmap) GetMap(key string) (Jmap, error) {
	if val, ok := m[key]; !ok {
		return nil, fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(map[string]interface{}); !ok {
		return nil, fmt.Errorf("`%s` was not a map, got %T", key, m[key])
	} else {
		return val, nil
	}
}

func (m Jmap) GetInt(key string) (int, error) {
	if val, ok := m[key]; !ok {
		return -1, fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(float64); !ok {
		return -1, fmt.Errorf("`%s` was not an int", key)
	} else {
		return int(val), nil
	}
}

func (m Jmap) GetBool(key string) (bool, error) {
	if val, ok := m[key]; !ok {
		return false, fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(bool); !ok {
		return false, fmt.Errorf("`%s` was not an int", key)
	} else {
		return val, nil
	}
}

func DebugJson(r *bytes.Buffer) {
	pretty := &bytes.Buffer{}
	if err := json.Indent(pretty, r.Bytes(), "\t", "\t"); err != nil {
		logger.Debugf("error indenting json: %s", err)
		return
	}

	// Print the JSON without the last "\n"
	str := pretty.String()
	logger.Debugf("\n\t%s", str[0:len(str)-1])
}
<commit_msg>shared/json: Removes DebugJson from shared<commit_after>package shared

import (
	"fmt"
)

type Jmap map[string]interface{}

func (m Jmap) GetString(key string) (string, error) {
	if val, ok := m[key]; !ok {
		return "", fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(string); !ok {
		return "", fmt.Errorf("`%s` was not a string", key)
	} else {
		return val, nil
	}
}

func (m Jmap) GetMap(key string) (Jmap, error) {
	if val, ok := m[key]; !ok {
		return nil, fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(map[string]interface{}); !ok {
		return nil, fmt.Errorf("`%s` was not a map, got %T", key, m[key])
	} else {
		return val, nil
	}
}

func (m Jmap) GetInt(key string) (int, error) {
	if val, ok := m[key]; !ok {
		return -1, fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(float64); !ok {
		return -1, fmt.Errorf("`%s` was not an int", key)
	} else {
		return int(val), nil
	}
}

func (m Jmap) GetBool(key string) (bool, error) {
	if val, ok := m[key]; !ok {
		return false, fmt.Errorf("Response was missing `%s`", key)
	} else if val, ok := val.(bool); !ok {
		return false, fmt.Errorf("`%s` was not an int", key)
	} else {
		return val, nil
	}
}
<|endoftext|>


<commit_before>// +build cgo
// run fake-arg-to-force-use-of-go-run

// Copyright 2020 The Go 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 main

// #include <stdlib.h>
// #include <unistd.h>
import "C"

import "os"

func main() {
	os.Setenv("FOO", "bar")
	s := C.GoString(C.getenv(C.CString("FOO")))
	if s != "bar" {
		panic("bad setenv, environment variable only has value \"" + s + "\"")
	}
	os.Unsetenv("FOO")
	s = C.GoString(C.getenv(C.CString("FOO")))
	if s != "" {
		panic("bad unsetenv, environment variable still has value \"" + s + "\"")
	}
}
<commit_msg>test: skip fixedbugs/issue36705 on Windows<commit_after>// +build cgo,!windows
// run fake-arg-to-force-use-of-go-run

// Copyright 2020 The Go 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 main

// #include <stdlib.h>
// #include <unistd.h>
import "C"

import "os"

func main() {
	os.Setenv("FOO", "bar")
	s := C.GoString(C.getenv(C.CString("FOO")))
	if s != "bar" {
		panic("bad setenv, environment variable only has value \"" + s + "\"")
	}
	os.Unsetenv("FOO")
	s = C.GoString(C.getenv(C.CString("FOO")))
	if s != "" {
		panic("bad unsetenv, environment variable still has value \"" + s + "\"")
	}
}
<|endoftext|>


<commit_before>package simra

import (
	"io"

	mp3 "github.com/hajimehoshi/go-mp3"
	"github.com/hajimehoshi/oto"
	"golang.org/x/mobile/asset"
)

type audio struct {
	player   *oto.Player
	dec      *mp3.Decoder
	resource asset.File
	isClosed chan bool
}

// Audioer is an interface for treating audio
type Audioer interface {
	Play(resource asset.File, loop bool, doneCallback func(err error)) error
	Stop() error
}

// NewAudio returns new audio instance that implements Audioer interface
func NewAudio() Audioer {
	return &audio{
		isClosed: make(chan bool, 1),
	}
}

func (a *audio) Play(resource asset.File, loop bool, doneCallback func(err error)) error {
	dec, err := mp3.NewDecoder(resource)
	if err != nil {
		return err
	}
	a.dec = dec

	player, err := oto.NewPlayer(dec.SampleRate(), 2, 2, 8192)
	if err != nil {
		return err
	}
	a.player = player

	a.resource = resource

	doneChan := make(chan error)
	go func() {
		doneChan <- a.doPlay(player, dec, loop)
	}()

	go func() {
		defer func() {
			err := dec.Close()
			if err != nil {
				LogError(err.Error())
			}
			err = player.Close()
			if err != nil {
				LogError(err.Error())
			}
		}()

		<-doneChan
		doneCallback(err)
	}()

	return nil
}

func (a *audio) doPlay(player *oto.Player, r io.ReadSeeker, loop bool) error {
	var offset, written int64
	var err error
	readByte := (int64)(4096 * 30)
loop:
	for {
	playback:
		for {
			r.Seek(offset, io.SeekStart)
			select {
			case <-a.isClosed:
				readByte = 0
				r.Seek(0, io.SeekEnd)
			default:
				// for non-blocking
			}
			written, err = io.CopyN(player, r, readByte)
			if err != nil || written == 0 {
				// error or EOF
				break playback
			}
			offset += written
		}
		if !loop || err != io.EOF {
			break loop
		}
		offset = 0
	}
	return err
}

func (a *audio) Stop() error {
	a.isClosed <- true
	return nil
}
<commit_msg>remove unnecessary member variables from audio.go<commit_after>package simra

import (
	"io"

	mp3 "github.com/hajimehoshi/go-mp3"
	"github.com/hajimehoshi/oto"
	"golang.org/x/mobile/asset"
)

type audio struct {
	isClosed chan bool
}

// Audioer is an interface for treating audio
type Audioer interface {
	Play(resource asset.File, loop bool, doneCallback func(err error)) error
	Stop() error
}

// NewAudio returns new audio instance that implements Audioer interface
func NewAudio() Audioer {
	return &audio{
		isClosed: make(chan bool, 1),
	}
}

func (a *audio) Play(resource asset.File, loop bool, doneCallback func(err error)) error {
	dec, err := mp3.NewDecoder(resource)
	if err != nil {
		return err
	}

	player, err := oto.NewPlayer(dec.SampleRate(), 2, 2, 8192)
	if err != nil {
		return err
	}

	doneChan := make(chan error)
	go func() {
		doneChan <- a.doPlay(player, dec, loop)
	}()

	go func() {
		defer func() {
			err := dec.Close()
			if err != nil {
				LogError(err.Error())
			}
			err = player.Close()
			if err != nil {
				LogError(err.Error())
			}
		}()

		<-doneChan
		doneCallback(err)
	}()

	return nil
}

func (a *audio) doPlay(player *oto.Player, r io.ReadSeeker, loop bool) error {
	var offset, written int64
	var err error
	readByte := (int64)(4096 * 30)
loop:
	for {
	playback:
		for {
			r.Seek(offset, io.SeekStart)
			select {
			case <-a.isClosed:
				readByte = 0
				r.Seek(0, io.SeekEnd)
			default:
				// for non-blocking
			}
			written, err = io.CopyN(player, r, readByte)
			if err != nil || written == 0 {
				// error or EOF
				break playback
			}
			offset += written
		}
		if !loop || err != io.EOF {
			break loop
		}
		offset = 0
	}
	return err
}

func (a *audio) Stop() error {
	a.isClosed <- true
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 Ole Krüger.
// Licensed under the MIT license which can be found in the LICENSE file.

package knxproj11

import (
	"archive/zip"
	"encoding/xml"
	"fmt"
	"path"
	"regexp"
)

// projectMetaDocument is used in conjunction with xml.Unmarshal to extract the meta information
// from a XML document.
type projectMetaDocument struct {
	Project struct {
		ID                 string `xml:"Id,attr"`
		ProjectInformation struct {
			Name   string `xml:"Name,attr"`
			Number uint   `xml:"ProjectId,attr"`
		}
	}
}

// readProjectMeta extracts project meta information from a file.
func readProjectMeta(file *zip.File) (meta projectMetaDocument, err error) {
	r, err := file.Open()
	if err != nil {
		return
	}

	err = xml.NewDecoder(r).Decode(&meta)
	r.Close()

	return
}

// ProjectMeta contains meta information for a project.
type ProjectMeta struct {
	file *zip.File
	ID   string
	Name string
}

// Process the project file in order to retrieve more information.
func (pm *ProjectMeta) Process() (Project, error) {
	return processZippedProject(pm.file)
}

// A ProjectArchive is a handle to a .knxproj file.
type ProjectArchive struct {
	archive  *zip.ReadCloser
	Projects []ProjectMeta
}

// projectFileRe is a regular expression that matches against project meta files.
var projectFileRe = regexp.MustCompile("^(p|P)-([0-9a-zA-Z]+)/(p|P)roject.xml$")

// Open a .knxproj file.
func Open(file string) (*ProjectArchive, error) {
	archive, err := zip.OpenReader(file)
	if err != nil {
		return nil, err
	}

	var projects []ProjectMeta

outerLoop:
	for _, file := range archive.File {
		// Is this a project meta file?
		if projectFileRe.MatchString(file.Name) {
			meta, err := readProjectMeta(file)
			if err != nil {
				archive.Close()
				return nil, err
			}

			// Determine the file name which contains the actual project.
			projectFile := path.Join(
				path.Dir(file.Name),
				fmt.Sprintf("%d.xml", meta.Project.ProjectInformation.Number),
			)

			for _, file := range archive.File {
				if file.Name == projectFile {
					projects = append(projects, ProjectMeta{
						file: file,
						ID:   meta.Project.ID,
						Name: meta.Project.ProjectInformation.Name,
					})

					continue outerLoop
				}
			}

			archive.Close()
			return nil, fmt.Errorf("Could not find file: %s", projectFile)
		}
	}

	return &ProjectArchive{
		archive:  archive,
		Projects: projects,
	}, nil
}

// Close the handle.
func (pz *ProjectArchive) Close() error {
	return pz.archive.Close()
}
<commit_msg>Let knxproj11's Open find manufacturer files<commit_after>// Copyright 2017 Ole Krüger.
// Licensed under the MIT license which can be found in the LICENSE file.

package knxproj11

import (
	"archive/zip"
	"encoding/xml"
	"fmt"
	"path"
	"regexp"
)

// projectMetaDocument is used in conjunction with xml.Unmarshal to extract the meta information
// from a XML document.
type projectMetaDocument struct {
	Project struct {
		ID                 string `xml:"Id,attr"`
		ProjectInformation struct {
			Name   string `xml:"Name,attr"`
			Number uint   `xml:"ProjectId,attr"`
		}
	}
}

// readProjectMeta extracts project meta information from a file.
func readProjectMeta(file *zip.File) (meta projectMetaDocument, err error) {
	r, err := file.Open()
	if err != nil {
		return
	}

	err = xml.NewDecoder(r).Decode(&meta)
	r.Close()

	return
}

// A ProjectFile contains project information.
type ProjectFile struct {
	file *zip.File
	ID   string
	Name string
}

// Process the project file in order to retrieve more information.
func (pm *ProjectFile) Process() (Project, error) {
	return processZippedProject(pm.file)
}

// findProjectFile analyzes the contents of the given project meta file in order to find the file
// that contains the actual project.
func findProjectFile(archive *zip.ReadCloser, file *zip.File) (_ ProjectFile, err error) {
	meta, err := readProjectMeta(file)
	if err != nil {
		return
	}

	// Determine the file name which contains the actual project.
	projectFile := path.Join(
		path.Dir(file.Name),
		fmt.Sprintf("%d.xml", meta.Project.ProjectInformation.Number),
	)

	// Find the project file.
	for _, file := range archive.File {
		if file.Name == projectFile {
			return ProjectFile{
				file: file,
				ID:   meta.Project.ID,
				Name: meta.Project.ProjectInformation.Name,
			}, nil
		}
	}

	err = fmt.Errorf(
		"Could not find project file '%s' from project '%s'",
		projectFile, meta.Project.ID,
	)
	return
}

// A ManufacturerFile contains manufacturerer data.
type ManufacturerFile struct {
	file *zip.File
}

// A ProjectArchive is a handle to a .knxproj file.
type ProjectArchive struct {
	archive       *zip.ReadCloser
	Projects      []ProjectFile
	Manufacturers []ManufacturerFile
}

var (
	projectFileRe      = regexp.MustCompile("^(p|P)-([0-9a-zA-Z]+)/(p|P)roject.xml$")
	manufacturerFileRe = regexp.MustCompile("^(m|M)-([0-9a-zA-Z]+)/(m|M)-([0-9a-zA-Z]+)([^.]+).xml$")

	// TODO: Figure out if '/' is a universal path seperator in ZIP files.
)

// Open a .knxproj file.
func Open(file string) (*ProjectArchive, error) {
	archive, err := zip.OpenReader(file)
	if err != nil {
		return nil, err
	}

	var projects []ProjectFile
	var manufacturers []ManufacturerFile

	for _, file := range archive.File {
		if projectFileRe.MatchString(file.Name) {
			proj, err := findProjectFile(archive, file)
			if err != nil {
				archive.Close()
				return nil, err
			}

			projects = append(projects, proj)
		} else if manufacturerFileRe.MatchString(file.Name) {
			manufacturers = append(manufacturers, ManufacturerFile{file: file})
		}
	}

	return &ProjectArchive{
		archive:       archive,
		Projects:      projects,
		Manufacturers: manufacturers,
	}, nil
}

// Close the handle.
func (pz *ProjectArchive) Close() error {
	return pz.archive.Close()
}
<|endoftext|>


<commit_before>/*
** Copyright [2013-2016] [Megam Systems]
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
 */
package bills

import (
	"encoding/json"
	"fmt"
	ldb "github.com/megamsys/libgo/db"
	constants "github.com/megamsys/libgo/utils"
	"strings"
)

const ACCOUNTSBUCKET = "accounts"

type Accounts struct {
	Id           string `json:"id" cql:"id"`
	Name         string `json:"name" cql:"name"`
	Phone        string `json:"phone" cql"phone"`
	Email        string `json:"email" cql:"email"`
	Dates        string `json:"dates" cql:"dates"`
	ApiKey       string `json:"api_key" cql:"api_key"`
	Password     string `json:"password" cql:"password"`
	Approval     string `json:"approval" cql:"approval"`
	Suspend      string `json:"suspend" cql:"suspend"`
	RegIpAddress string `json:"registration_ip_address" cql:"registration_ip_address"`
	States       string `json:"states" cql:"states"`
}

type BillAccounts struct {
	Id           string   `json:"id" cql:"id"`
	Name         Name     `json:"name" cql:"name"`
	Phone        Phone    `json:"phone" cql:"phone"`
	Email        string   `json:"email" cql:"email"`
	Dates        Dates    `json:"dates" cql:"dates"`
	ApiKey       string   `json:"api_key" cql:"api_key"`
	Password     Password `json:"password" cql:"password"`
	Approval     Approval `json:"approval" cql:"approval"`
	Suspend      Suspend  `json:"suspend" cql:"suspend"`
	RegIpAddress string   `json:"registration_ip_address" cql:"registration_ip_address"`
	States       States   `json:"states" cql:"states"`
}

type Organization struct {
	Id        string `json:"id" cql:"id"`
	AccountsId string `json:"accounts_id" cql:"accounts_id"`
	CreatedAt string `json:"created_at" cql:"created_at"`
	JsonClaz  string `json:"json_claz" cql:"json_claz"`
	Name      string `json:"name" cql:"name"`
}

type Name struct {
	FirstName string `json:"first_name" cql:"first_name"`
	LastName  string `json:"last_name" cql:"last_name"`
}

type Password struct {
	Password            string `json:"password" cql:"password"`
	PasswordResetKey    string `json:"password_reset_key" cql:"password_reset_key"`
	PasswordResetSentAt string `json:"password_reset_sent_at" cql:"password_reset_sent_at"`
}

type Phone struct {
	Phone         string `json:"phone" cql:"phone"`
	PhoneVerified string `json:"phone_verified" cql:"phone_verified"`
}

type Approval struct {
	Approved     string `json:"approved" cql:"approved"`
	ApprovedById string `json:"approved_by_id" cql:"approved_by_id"`
	ApprovedAt   string `json:"approved_at" cql:"approved_at"`
}

type Suspend struct {
	Suspended     string `json:"suspended" cql:"suspended"`
	SuspendedAt   string `json:"suspended_at" cql:"suspended_at"`
	SuspendedTill string `json:"suspended_till" cql:"suspended_till"`
}

type Dates struct {
	CreatedAt       string `json:"created_at" cql:"created_at"`
	LastPostedAt    string `json:"last_posted_at" cql:"last_posted_at"`
	LastEmailedAt   string `json:"last_emailed_at" cql:"last_emailed_at"`
	PreviousVisitAt string `json:"previous_visit_at" cql:"previous_visit_at"`
	FirstSeenAt     string `json:"first_seen_at" cql:"first_seen_at"`
}

type States struct {
	Authority string `json:"authority" cql:"authority"`
	Active    string `json:"active" cql:"active"`
	Blocked   string `json:"blocked" cql:"blocked"`
	Staged    string `json:"staged" cql:"staged"`
}

func NewAccounts(email string, m map[string]string) (*Accounts, error) {
	a := &Accounts{}
	ops := ldb.Options{
		TableName:   ACCOUNTSBUCKET,
		Pks:         []string{},
		Ccms:        []string{"email"},
		Hosts:       strings.Split(m[constants.SCYLLAHOST], ","),
		Keyspace:    m[constants.SCYLLAKEYSPACE],
		PksClauses:  make(map[string]interface{}),
		CcmsClauses: map[string]interface{}{"email": email},
	}
	if err := ldb.Fetchdb(ops, a); err != nil {
		return nil, err
	}
	return a, nil
}

func AccountsOrg(email string, m map[string]string) (*Accounts, error) {
	org := &Organization{}
	ops := ldb.Options{
		TableName:   ORGANIZATIONBUCKET,
		Pks:         []string{},
		Ccms:        []string{"email"},
		Hosts:       strings.Split(m[constants.SCYLLAHOST], ","),
		Keyspace:    m[constants.SCYLLAKEYSPACE],
		PksClauses:  make(map[string]interface{}),
		CcmsClauses: map[string]interface{}{"email": email},
	}
	if err := ldb.Fetchdb(ops, org); err != nil {
		return nil, err
	}
	return org, nil
}

func (a *Accounts) convertBillAccount() (*BillAccounts, error) {
	b := &BillAccounts{}
	a.parseStringToStruct([]byte(a.Name), &b.Name)
	a.parseStringToStruct([]byte(a.Phone), &b.Phone)
	a.parseStringToStruct([]byte(a.Password), &b.Password)
	a.parseStringToStruct([]byte(a.Suspend), &b.Suspend)
	a.parseStringToStruct([]byte(a.Approval), &b.Approval)
	a.parseStringToStruct([]byte(a.States), &b.States)
	a.parseStringToStruct([]byte(a.Dates), &b.Dates)
	b.Id = a.Id
	b.Email = a.Email
	b.ApiKey = a.ApiKey
	b.RegIpAddress = a.RegIpAddress
	return b, nil
}

func (a *Accounts) parseStringToStruct(b []byte, i interface{}) {
	err := json.Unmarshal(b, i)
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>return orgs<commit_after>/*
** Copyright [2013-2016] [Megam Systems]
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
 */
package bills

import (
	"encoding/json"
	"fmt"
	ldb "github.com/megamsys/libgo/db"
	constants "github.com/megamsys/libgo/utils"
	"strings"
)

const(
	ORGANIZATIONBUCKET ="organizations"
	ACCOUNTSBUCKET = "accounts"
	)

type Accounts struct {
	Id           string `json:"id" cql:"id"`
	Name         string `json:"name" cql:"name"`
	Phone        string `json:"phone" cql"phone"`
	Email        string `json:"email" cql:"email"`
	Dates        string `json:"dates" cql:"dates"`
	ApiKey       string `json:"api_key" cql:"api_key"`
	Password     string `json:"password" cql:"password"`
	Approval     string `json:"approval" cql:"approval"`
	Suspend      string `json:"suspend" cql:"suspend"`
	RegIpAddress string `json:"registration_ip_address" cql:"registration_ip_address"`
	States       string `json:"states" cql:"states"`
}

type BillAccounts struct {
	Id           string   `json:"id" cql:"id"`
	Name         Name     `json:"name" cql:"name"`
	Phone        Phone    `json:"phone" cql:"phone"`
	Email        string   `json:"email" cql:"email"`
	Dates        Dates    `json:"dates" cql:"dates"`
	ApiKey       string   `json:"api_key" cql:"api_key"`
	Password     Password `json:"password" cql:"password"`
	Approval     Approval `json:"approval" cql:"approval"`
	Suspend      Suspend  `json:"suspend" cql:"suspend"`
	RegIpAddress string   `json:"registration_ip_address" cql:"registration_ip_address"`
	States       States   `json:"states" cql:"states"`
}

type Organization struct {
	Id        string `json:"id" cql:"id"`
	AccountsId string `json:"accounts_id" cql:"accounts_id"`
	CreatedAt string `json:"created_at" cql:"created_at"`
	JsonClaz  string `json:"json_claz" cql:"json_claz"`
	Name      string `json:"name" cql:"name"`
}

type Name struct {
	FirstName string `json:"first_name" cql:"first_name"`
	LastName  string `json:"last_name" cql:"last_name"`
}

type Password struct {
	Password            string `json:"password" cql:"password"`
	PasswordResetKey    string `json:"password_reset_key" cql:"password_reset_key"`
	PasswordResetSentAt string `json:"password_reset_sent_at" cql:"password_reset_sent_at"`
}

type Phone struct {
	Phone         string `json:"phone" cql:"phone"`
	PhoneVerified string `json:"phone_verified" cql:"phone_verified"`
}

type Approval struct {
	Approved     string `json:"approved" cql:"approved"`
	ApprovedById string `json:"approved_by_id" cql:"approved_by_id"`
	ApprovedAt   string `json:"approved_at" cql:"approved_at"`
}

type Suspend struct {
	Suspended     string `json:"suspended" cql:"suspended"`
	SuspendedAt   string `json:"suspended_at" cql:"suspended_at"`
	SuspendedTill string `json:"suspended_till" cql:"suspended_till"`
}

type Dates struct {
	CreatedAt       string `json:"created_at" cql:"created_at"`
	LastPostedAt    string `json:"last_posted_at" cql:"last_posted_at"`
	LastEmailedAt   string `json:"last_emailed_at" cql:"last_emailed_at"`
	PreviousVisitAt string `json:"previous_visit_at" cql:"previous_visit_at"`
	FirstSeenAt     string `json:"first_seen_at" cql:"first_seen_at"`
}

type States struct {
	Authority string `json:"authority" cql:"authority"`
	Active    string `json:"active" cql:"active"`
	Blocked   string `json:"blocked" cql:"blocked"`
	Staged    string `json:"staged" cql:"staged"`
}

func NewAccounts(email string, m map[string]string) (*Accounts, error) {
	a := &Accounts{}
	ops := ldb.Options{
		TableName:   ACCOUNTSBUCKET,
		Pks:         []string{},
		Ccms:        []string{"email"},
		Hosts:       strings.Split(m[constants.SCYLLAHOST], ","),
		Keyspace:    m[constants.SCYLLAKEYSPACE],
		PksClauses:  make(map[string]interface{}),
		CcmsClauses: map[string]interface{}{"email": email},
	}
	if err := ldb.Fetchdb(ops, a); err != nil {
		return nil, err
	}
	return a, nil
}

func AccountsOrg(email string, m map[string]string) (*Organization, error) {
	org := &Organization{}
	ops := ldb.Options{
		TableName:   ORGANIZATIONBUCKET,
		Pks:         []string{},
		Ccms:        []string{"email"},
		Hosts:       strings.Split(m[constants.SCYLLAHOST], ","),
		Keyspace:    m[constants.SCYLLAKEYSPACE],
		PksClauses:  make(map[string]interface{}),
		CcmsClauses: map[string]interface{}{"email": email},
	}
	if err := ldb.Fetchdb(ops, org); err != nil {
		return nil, err
	}
	return org, nil
}

func (a *Accounts) convertBillAccount() (*BillAccounts, error) {
	b := &BillAccounts{}
	a.parseStringToStruct([]byte(a.Name), &b.Name)
	a.parseStringToStruct([]byte(a.Phone), &b.Phone)
	a.parseStringToStruct([]byte(a.Password), &b.Password)
	a.parseStringToStruct([]byte(a.Suspend), &b.Suspend)
	a.parseStringToStruct([]byte(a.Approval), &b.Approval)
	a.parseStringToStruct([]byte(a.States), &b.States)
	a.parseStringToStruct([]byte(a.Dates), &b.Dates)
	b.Id = a.Id
	b.Email = a.Email
	b.ApiKey = a.ApiKey
	b.RegIpAddress = a.RegIpAddress
	return b, nil
}

func (a *Accounts) parseStringToStruct(b []byte, i interface{}) {
	err := json.Unmarshal(b, i)
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package experimental

import (
	"fmt"

	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("v3-restart-app-instance command", func() {
	var (
		orgName   string
		spaceName string
		appName   string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		appName = helpers.PrefixedRandomName("app")
	})

	Context("when --help flag is set", func() {
		It("Displays command usage to output", func() {
			session := helpers.CF("v3-restart-app-instance", "--help")
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Say("v3-restart-app-instance - Terminate, then instantiate an app instance"))
			Eventually(session).Should(Say("USAGE:"))
			Eventually(session).Should(Say(`cf v3-restart-app-instance APP_NAME INDEX [--process PROCESS]`))
			Eventually(session).Should(Say("SEE ALSO:"))
			Eventually(session).Should(Say("v3-restart"))
			Eventually(session).Should(Exit(0))
		})
	})

	Context("when the app name is not provided", func() {
		It("tells the user that the app name is required, prints help text, and exits 1", func() {
			session := helpers.CF("v3-restart-app-instance")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required arguments `APP_NAME` and `INDEX` were not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("when the index is not provided", func() {
		It("tells the user that the index is required, prints help text, and exits 1", func() {
			session := helpers.CF("v3-restart-app-instance", appName)

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `INDEX` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	It("displays the experimental warning", func() {
		session := helpers.CF("v3-restart-app-instance", appName, "1")
		Eventually(session.Err).Should(Say("This command is in EXPERIMENTAL stage and may change without notice"))
		Eventually(session).Should(Exit())
	})

	Context("when the environment is not setup correctly", func() {
		Context("when no API endpoint is set", func() {
			BeforeEach(func() {
				helpers.UnsetAPI()
			})

			It("fails with no API endpoint set message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No API endpoint set. Use 'cf login' or 'cf api' to target an endpoint."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the v3 api does not exist", func() {
			var server *Server

			BeforeEach(func() {
				server = helpers.StartAndTargetServerWithoutV3API()
			})

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

			It("fails with error message that the minimum version is not met", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("This command requires CF API version 3\\.27\\.0 or higher\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the v3 api version is lower than the minimum version", func() {
			var server *Server

			BeforeEach(func() {
				server = helpers.StartAndTargetServerWithAPIVersions(helpers.DefaultV2Version, "3.0.0")
			})

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

			It("fails with error message that the minimum version is not met", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("This command requires CF API version 3\\.27\\.0 or higher\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when not logged in", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
			})

			It("fails with not logged in message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("Not logged in. Use 'cf login' to log in."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no org set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
			})

			It("fails with no targeted org error message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No org targeted, use 'cf target -o ORG' to target an org."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no space set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
				helpers.TargetOrg(ReadOnlyOrg)
			})

			It("fails with no targeted space error message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No space targeted, use 'cf target -s SPACE' to target a space."))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the environment is setup correctly", func() {
		var userName string

		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			userName, _ = helpers.GetCredentials()
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		Context("when app does not exist", func() {
			It("fails with error", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "0", "--process", "some-process")
				Eventually(session).Should(Say("Restarting instance 0 of process some-process of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
				Eventually(session.Err).Should(Say("App %s not found", appName))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when app exists", func() {
			BeforeEach(func() {
				helpers.WithProcfileApp(func(appDir string) {
					Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-push", appName)).Should(Exit(0))
				})
			})

			Context("when process type is not provided", func() {
				It("defaults to web process", func() {
					appOutputSession := helpers.CF("app", appName)
					Eventually(appOutputSession).Should(Exit(0))
					firstAppTable := helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())

					session := helpers.CF("v3-restart-app-instance", appName, "0")
					Eventually(session).Should(Say("Restarting instance 0 of process web of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
					Eventually(session).Should(Say("OK"))
					Eventually(session).Should(Exit(0))

					Eventually(func() string {
						var restartedAppTable helpers.AppTable
						Eventually(func() string {
							appOutputSession := helpers.CF("app", appName)
							Eventually(appOutputSession).Should(Exit(0))
							restartedAppTable = helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())

							if len(restartedAppTable.Processes) > 0 {
								return restartedAppTable.Processes[0].Type
							}

							return ""
						}).Should(Equal(`web`))
						Expect(restartedAppTable.Processes[0].Instances).ToNot(BeEmpty())
						return restartedAppTable.Processes[0].Instances[0].Since
					}).ShouldNot(Equal(firstAppTable.Processes[0].Instances[0].Since))
				})
			})

			Context("when a process type is provided", func() {
				Context("when the process type does not exist", func() {
					It("fails with error", func() {
						session := helpers.CF("v3-restart-app-instance", appName, "0", "--process", "unknown-process")
						Eventually(session).Should(Say("Restarting instance 0 of process unknown-process of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
						Eventually(session.Err).Should(Say("Process unknown-process not found"))
						Eventually(session).Should(Exit(1))
					})
				})

				Context("when the process type exists", func() {
					Context("when instance index exists", func() {
						findConsoleProcess := func(appTable helpers.AppTable) (helpers.AppProcessTable, bool) {
							for _, process := range appTable.Processes {
								if process.Type == "console" {
									return process, true
								}
							}
							return helpers.AppProcessTable{}, false
						}

						It("defaults to requested process", func() {
							By("scaling worker process to 1 instance")
							session := helpers.CF("v3-scale", appName, "--process", "console", "-i", "1")
							Eventually(session).Should(Exit(0))

							By("waiting for worker process to come up")
							var firstAppTableConsoleProcess helpers.AppProcessTable
							Eventually(func() string {
								appOutputSession := helpers.CF("app", appName)
								Eventually(appOutputSession).Should(Exit(0))
								firstAppTable := helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())

								var found bool
								firstAppTableConsoleProcess, found = findConsoleProcess(firstAppTable)
								Expect(found).To(BeTrue())
								return fmt.Sprintf("%s, %s", firstAppTableConsoleProcess.Type, firstAppTableConsoleProcess.InstanceCount)
							}).Should(MatchRegexp(`console, 1/1`))

							By("restarting worker process instance")
							session = helpers.CF("v3-restart-app-instance", appName, "0", "--process", "console")
							Eventually(session).Should(Say("Restarting instance 0 of process console of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
							Eventually(session).Should(Say("OK"))
							Eventually(session).Should(Exit(0))

							By("waiting for restarted process instance to come up")
							Eventually(func() string {
								var restartedAppTableConsoleProcess helpers.AppProcessTable

								Eventually(func() string {
									appOutputSession := helpers.CF("app", appName)
									Eventually(appOutputSession).Should(Exit(0))

									restartedAppTable := helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())
									var found bool
									restartedAppTableConsoleProcess, found = findConsoleProcess(restartedAppTable)
									Expect(found).To(BeTrue())

									return fmt.Sprintf("%s, %s", restartedAppTableConsoleProcess.Type, restartedAppTableConsoleProcess.InstanceCount)
								}).Should(MatchRegexp(`console, 1/1`))

								return restartedAppTableConsoleProcess.Instances[0].Since
							}).ShouldNot(Equal(firstAppTableConsoleProcess.Instances[0].Since))
						})
					})

					Context("when instance index does not exist", func() {
						It("fails with error", func() {
							session := helpers.CF("v3-restart-app-instance", appName, "42", "--process", constant.ProcessTypeWeb)
							Eventually(session).Should(Say("Restarting instance 42 of process web of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
							Eventually(session.Err).Should(Say("Instance 42 of process web not found"))
							Eventually(session).Should(Exit(1))
						})
					})
				})
			})
		})
	})
})
<commit_msg>fix integration test<commit_after>package experimental

import (
	"fmt"

	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("v3-restart-app-instance command", func() {
	var (
		orgName   string
		spaceName string
		appName   string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		appName = helpers.PrefixedRandomName("app")
	})

	Context("when --help flag is set", func() {
		It("Displays command usage to output", func() {
			session := helpers.CF("v3-restart-app-instance", "--help")
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Say("v3-restart-app-instance - Terminate, then instantiate an app instance"))
			Eventually(session).Should(Say("USAGE:"))
			Eventually(session).Should(Say(`cf v3-restart-app-instance APP_NAME INDEX [--process PROCESS]`))
			Eventually(session).Should(Say("SEE ALSO:"))
			Eventually(session).Should(Say("v3-restart"))
			Eventually(session).Should(Exit(0))
		})
	})

	Context("when the app name is not provided", func() {
		It("tells the user that the app name is required, prints help text, and exits 1", func() {
			session := helpers.CF("v3-restart-app-instance")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required arguments `APP_NAME` and `INDEX` were not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("when the index is not provided", func() {
		It("tells the user that the index is required, prints help text, and exits 1", func() {
			session := helpers.CF("v3-restart-app-instance", appName)

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `INDEX` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	It("displays the experimental warning", func() {
		session := helpers.CF("v3-restart-app-instance", appName, "1")
		Eventually(session.Err).Should(Say("This command is in EXPERIMENTAL stage and may change without notice"))
		Eventually(session).Should(Exit())
	})

	Context("when the environment is not setup correctly", func() {
		Context("when no API endpoint is set", func() {
			BeforeEach(func() {
				helpers.UnsetAPI()
			})

			It("fails with no API endpoint set message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No API endpoint set. Use 'cf login' or 'cf api' to target an endpoint."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the v3 api does not exist", func() {
			var server *Server

			BeforeEach(func() {
				server = helpers.StartAndTargetServerWithoutV3API()
			})

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

			It("fails with error message that the minimum version is not met", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("This command requires CF API version 3\\.27\\.0 or higher\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when the v3 api version is lower than the minimum version", func() {
			var server *Server

			BeforeEach(func() {
				server = helpers.StartAndTargetServerWithAPIVersions(helpers.DefaultV2Version, "3.0.0")
			})

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

			It("fails with error message that the minimum version is not met", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("This command requires CF API version 3\\.27\\.0 or higher\\."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when not logged in", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
			})

			It("fails with not logged in message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("Not logged in. Use 'cf login' to log in."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no org set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
			})

			It("fails with no targeted org error message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No org targeted, use 'cf target -o ORG' to target an org."))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when there is no space set", func() {
			BeforeEach(func() {
				helpers.LogoutCF()
				helpers.LoginCF()
				helpers.TargetOrg(ReadOnlyOrg)
			})

			It("fails with no targeted space error message", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "1")
				Eventually(session).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("No space targeted, use 'cf target -s SPACE' to target a space."))
				Eventually(session).Should(Exit(1))
			})
		})
	})

	Context("when the environment is setup correctly", func() {
		var userName string

		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			userName, _ = helpers.GetCredentials()
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		Context("when app does not exist", func() {
			It("fails with error", func() {
				session := helpers.CF("v3-restart-app-instance", appName, "0", "--process", "some-process")
				Eventually(session).Should(Say("Restarting instance 0 of process some-process of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
				Eventually(session.Err).Should(Say("App %s not found", appName))
				Eventually(session).Should(Exit(1))
			})
		})

		Context("when app exists", func() {
			BeforeEach(func() {
				helpers.WithProcfileApp(func(appDir string) {
					Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-push", appName)).Should(Exit(0))
				})
			})

			Context("when process type is not provided", func() {
				It("defaults to web process", func() {
					appOutputSession := helpers.CF("app", appName)
					Eventually(appOutputSession).Should(Exit(0))
					firstAppTable := helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())

					session := helpers.CF("v3-restart-app-instance", appName, "0")
					Eventually(session).Should(Say("Restarting instance 0 of process web of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
					Eventually(session).Should(Say("OK"))
					Eventually(session).Should(Exit(0))

					Eventually(func() string {
						var restartedAppTable helpers.AppTable
						Eventually(func() string {
							appOutputSession := helpers.CF("app", appName)
							Eventually(appOutputSession).Should(Exit(0))
							restartedAppTable = helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())

							if len(restartedAppTable.Processes) > 0 {
								return fmt.Sprintf("%s, %s", restartedAppTable.Processes[0].Type, restartedAppTable.Processes[0].InstanceCount)
							}

							return ""
						}).Should(Equal(`web, 1/1`))
						Expect(restartedAppTable.Processes[0].Instances).ToNot(BeEmpty())
						return restartedAppTable.Processes[0].Instances[0].Since
					}).ShouldNot(Equal(firstAppTable.Processes[0].Instances[0].Since))
				})
			})

			Context("when a process type is provided", func() {
				Context("when the process type does not exist", func() {
					It("fails with error", func() {
						session := helpers.CF("v3-restart-app-instance", appName, "0", "--process", "unknown-process")
						Eventually(session).Should(Say("Restarting instance 0 of process unknown-process of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
						Eventually(session.Err).Should(Say("Process unknown-process not found"))
						Eventually(session).Should(Exit(1))
					})
				})

				Context("when the process type exists", func() {
					Context("when instance index exists", func() {
						findConsoleProcess := func(appTable helpers.AppTable) (helpers.AppProcessTable, bool) {
							for _, process := range appTable.Processes {
								if process.Type == "console" {
									return process, true
								}
							}
							return helpers.AppProcessTable{}, false
						}

						It("defaults to requested process", func() {
							By("scaling worker process to 1 instance")
							session := helpers.CF("v3-scale", appName, "--process", "console", "-i", "1")
							Eventually(session).Should(Exit(0))

							By("waiting for worker process to come up")
							var firstAppTableConsoleProcess helpers.AppProcessTable
							Eventually(func() string {
								appOutputSession := helpers.CF("app", appName)
								Eventually(appOutputSession).Should(Exit(0))
								firstAppTable := helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())

								var found bool
								firstAppTableConsoleProcess, found = findConsoleProcess(firstAppTable)
								Expect(found).To(BeTrue())
								return fmt.Sprintf("%s, %s", firstAppTableConsoleProcess.Type, firstAppTableConsoleProcess.InstanceCount)
							}).Should(MatchRegexp(`console, 1/1`))

							By("restarting worker process instance")
							session = helpers.CF("v3-restart-app-instance", appName, "0", "--process", "console")
							Eventually(session).Should(Say("Restarting instance 0 of process console of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
							Eventually(session).Should(Say("OK"))
							Eventually(session).Should(Exit(0))

							By("waiting for restarted process instance to come up")
							Eventually(func() string {
								var restartedAppTableConsoleProcess helpers.AppProcessTable

								Eventually(func() string {
									appOutputSession := helpers.CF("app", appName)
									Eventually(appOutputSession).Should(Exit(0))

									restartedAppTable := helpers.ParseV3AppProcessTable(appOutputSession.Out.Contents())
									var found bool
									restartedAppTableConsoleProcess, found = findConsoleProcess(restartedAppTable)
									Expect(found).To(BeTrue())

									return fmt.Sprintf("%s, %s", restartedAppTableConsoleProcess.Type, restartedAppTableConsoleProcess.InstanceCount)
								}).Should(MatchRegexp(`console, 1/1`))

								return restartedAppTableConsoleProcess.Instances[0].Since
							}).ShouldNot(Equal(firstAppTableConsoleProcess.Instances[0].Since))
						})
					})

					Context("when instance index does not exist", func() {
						It("fails with error", func() {
							session := helpers.CF("v3-restart-app-instance", appName, "42", "--process", constant.ProcessTypeWeb)
							Eventually(session).Should(Say("Restarting instance 42 of process web of app %s in org %s / space %s as %s", appName, orgName, spaceName, userName))
							Eventually(session.Err).Should(Say("Instance 42 of process web not found"))
							Eventually(session).Should(Exit(1))
						})
					})
				})
			})
		})
	})
})
<|endoftext|>