// Copyright 2013 go-dockerclient 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 docker

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"time"

	"golang.org/x/crypto/ssh/terminal"
)

func TestNewAPIClient(t *testing.T) {
	t.Parallel()
	endpoint := "http://localhost:4243"
	client, err := NewClient(endpoint)
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	// test native endpoints
	endpoint = nativeRealEndpoint
	client, err = NewClient(endpoint)
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if !client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be true, got false")
	}
	if client.requestedAPIVersion != nil {
		t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion)
	}
}

func newTLSClient(endpoint string) (*Client, error) {
	return NewTLSClient(endpoint,
		"testing/data/cert.pem",
		"testing/data/key.pem",
		"testing/data/ca.pem")
}

func TestNewTSLAPIClient(t *testing.T) {
	t.Parallel()
	endpoint := "https://localhost:4243"
	client, err := newTLSClient(endpoint)
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if !client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be true, got false")
	}
	if client.requestedAPIVersion != nil {
		t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion)
	}
}

func TestNewVersionedClient(t *testing.T) {
	t.Parallel()
	endpoint := "http://localhost:4243"
	client, err := NewVersionedClient(endpoint, "1.12")
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" {
		t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion)
	}
	if client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be false, got true")
	}
}

func TestNewVersionedClientFromEnv(t *testing.T) {
	t.Parallel()
	endpoint := "tcp://localhost:2376"
	endpointURL := "http://localhost:2376"
	os.Setenv("DOCKER_HOST", endpoint)
	os.Setenv("DOCKER_TLS_VERIFY", "")
	client, err := NewVersionedClientFromEnv("1.12")
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if client.endpointURL.String() != endpointURL {
		t.Errorf("Expected endpointURL %s. Got %s.", endpoint, client.endpoint)
	}
	if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" {
		t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion)
	}
	if client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be false, got true")
	}
}

func TestNewVersionedClientFromEnvTLS(t *testing.T) {
	t.Parallel()
	endpoint := "tcp://localhost:2376"
	endpointURL := "https://localhost:2376"
	base, _ := os.Getwd()
	os.Setenv("DOCKER_CERT_PATH", filepath.Join(base, "/testing/data/"))
	os.Setenv("DOCKER_HOST", endpoint)
	os.Setenv("DOCKER_TLS_VERIFY", "1")
	client, err := NewVersionedClientFromEnv("1.12")
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if client.endpointURL.String() != endpointURL {
		t.Errorf("Expected endpointURL %s. Got %s.", endpoint, client.endpoint)
	}
	if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" {
		t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion)
	}
	if client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be false, got true")
	}
}

func TestNewTLSVersionedClient(t *testing.T) {
	t.Parallel()
	certPath := "testing/data/cert.pem"
	keyPath := "testing/data/key.pem"
	caPath := "testing/data/ca.pem"
	endpoint := "https://localhost:4243"
	client, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.14" {
		t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.14", reqVersion)
	}
	if client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be false, got true")
	}
}

func TestNewTLSVersionedClientNoClientCert(t *testing.T) {
	t.Parallel()
	certPath := "testing/data/cert_doesnotexist.pem"
	keyPath := "testing/data/key_doesnotexist.pem"
	caPath := "testing/data/ca.pem"
	endpoint := "https://localhost:4243"
	client, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
	if err != nil {
		t.Fatal(err)
	}
	if client.endpoint != endpoint {
		t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
	}
	if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.14" {
		t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.14", reqVersion)
	}
	if client.SkipServerVersionCheck {
		t.Error("Expected SkipServerVersionCheck to be false, got true")
	}
}

func TestNewTLSVersionedClientInvalidCA(t *testing.T) {
	t.Parallel()
	certPath := "testing/data/cert.pem"
	keyPath := "testing/data/key.pem"
	caPath := "testing/data/key.pem"
	endpoint := "https://localhost:4243"
	_, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
	if err == nil {
		t.Errorf("Expected invalid ca at %s", caPath)
	}
}

func TestNewTLSVersionedClientInvalidCANoClientCert(t *testing.T) {
	t.Parallel()
	certPath := "testing/data/cert_doesnotexist.pem"
	keyPath := "testing/data/key_doesnotexist.pem"
	caPath := "testing/data/key.pem"
	endpoint := "https://localhost:4243"
	_, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14")
	if err == nil {
		t.Errorf("Expected invalid ca at %s", caPath)
	}
}

func TestNewClientInvalidEndpoint(t *testing.T) {
	t.Parallel()
	cases := []string{
		"htp://localhost:3243", "http://localhost:a",
		"", "http://localhost:8080:8383", "http://localhost:65536",
		"https://localhost:-20",
	}
	for _, c := range cases {
		testCase := c
		t.Run(testCase, func(t *testing.T) {
			t.Parallel()
			client, err := NewClient(testCase)
			if client != nil {
				t.Errorf("Want <nil> client for invalid endpoint, got %#v.", client)
			}
			if !errors.Is(err, ErrInvalidEndpoint) {
				t.Errorf("NewClient(%q): Got invalid error for invalid endpoint. Want %#v. Got %#v.", testCase, ErrInvalidEndpoint, err)
			}
		})
	}
}

func TestNewClientNoSchemeEndpoint(t *testing.T) {
	t.Parallel()
	cases := []string{"localhost", "localhost:8080"}
	for _, c := range cases {
		testCase := c
		t.Run(testCase, func(t *testing.T) {
			client, err := NewClient(testCase)
			if client == nil {
				t.Errorf("Want client for scheme-less endpoint, got <nil>")
			}
			if err != nil {
				t.Errorf("Got unexpected error scheme-less endpoint: %q", err)
			}
		})
	}
}

func TestNewTLSClient(t *testing.T) {
	t.Parallel()
	tests := []struct {
		endpoint string
		expected string
	}{
		{"tcp://localhost:2376", "https"},
		{"tcp://localhost:2375", "https"},
		{"tcp://localhost:4000", "https"},
		{"http://localhost:4000", "https"},
	}
	for _, tt := range tests {
		test := tt
		t.Run(test.endpoint, func(t *testing.T) {
			t.Parallel()
			client, err := newTLSClient(test.endpoint)
			if err != nil {
				t.Error(err)
			}
			got := client.endpointURL.Scheme
			if got != test.expected {
				t.Errorf("endpointURL.Scheme: Got %s. Want %s.", got, test.expected)
			}
		})
	}
}

func TestEndpoint(t *testing.T) {
	t.Parallel()
	client, err := NewVersionedClient("http://localhost:4243", "1.12")
	if err != nil {
		t.Fatal(err)
	}
	if endpoint := client.Endpoint(); endpoint != client.endpoint {
		t.Errorf("Client.Endpoint(): want %q. Got %q", client.endpoint, endpoint)
	}
}

func TestGetURL(t *testing.T) {
	t.Parallel()
	tests := []struct {
		endpoint string
		path     string
		expected string
	}{
		{"http://localhost:4243/", "/", "http://localhost:4243/"},
		{"http://localhost:4243", "/", "http://localhost:4243/"},
		{"http://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"},
		{"tcp://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"},
		{"http://localhost:4243/////", "/", "http://localhost:4243/"},
		{nativeRealEndpoint, "/containers", "/containers"},
	}
	for _, tt := range tests {
		test := tt
		t.Run(test.endpoint+test.path, func(t *testing.T) {
			t.Parallel()
			client, _ := NewClient(test.endpoint)
			client.endpoint = test.endpoint
			client.SkipServerVersionCheck = true
			got := client.getURL(test.path)
			if got != test.expected {
				t.Errorf("getURL(%q): Got %s. Want %s.", test.path, got, test.expected)
			}
		})
	}
}

func TestGetFakeNativeURL(t *testing.T) {
	t.Parallel()
	tests := []struct {
		endpoint string
		path     string
		expected string
	}{
		{nativeRealEndpoint, "/", "http://unix.sock/"},
		{nativeRealEndpoint, "/", "http://unix.sock/"},
		{nativeRealEndpoint, "/containers/ps", "http://unix.sock/containers/ps"},
	}
	for _, tt := range tests {
		test := tt
		t.Run(test.path, func(t *testing.T) {
			t.Parallel()
			client, _ := NewClient(test.endpoint)
			client.endpoint = test.endpoint
			client.SkipServerVersionCheck = true
			got := client.getFakeNativeURL(test.path)
			if got != test.expected {
				t.Errorf("getURL(%q): Got %s. Want %s.", test.path, got, test.expected)
			}
		})
	}
}

func TestError(t *testing.T) {
	t.Parallel()
	fakeBody := ioutil.NopCloser(bytes.NewBufferString("bad parameter"))
	resp := &http.Response{
		StatusCode: 400,
		Body:       fakeBody,
	}
	err := newError(resp)
	expected := Error{Status: 400, Message: "bad parameter"}
	if !reflect.DeepEqual(expected, *err) {
		t.Errorf("Wrong error type. Want %#v. Got %#v.", expected, *err)
	}
	message := "API error (400): bad parameter"
	if err.Error() != message {
		t.Errorf("Wrong error message. Want %q. Got %q.", message, err.Error())
	}
}

func TestQueryString(t *testing.T) {
	t.Parallel()
	v := float32(2.4)
	f32QueryString := fmt.Sprintf("w=%s&x=10&y=10.35", strconv.FormatFloat(float64(v), 'f', -1, 64))
	jsonPerson := url.QueryEscape(`{"Name":"gopher","age":4}`)
	tests := []struct {
		input   interface{}
		want    string
		wantAPI APIVersion
	}{
		{&ListContainersOptions{All: true}, "all=1", nil},
		{ListContainersOptions{All: true}, "all=1", nil},
		{ListContainersOptions{Before: "something"}, "before=something", nil},
		{ListContainersOptions{Before: "something", Since: "other"}, "before=something&since=other", nil},
		{ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}}, "filters=%7B%22status%22%3A%5B%22paused%22%2C%22running%22%5D%7D", nil},
		{dumb{X: 10, Y: 10.35000}, "x=10&y=10.35", apiVersion119},
		{dumb{W: v, X: 10, Y: 10.35000}, f32QueryString, apiVersion124},
		{dumb{X: 10, Y: 10.35000, Z: 10}, "x=10&y=10.35&zee=10", apiVersion119},
		{dumb{v: 4, X: 10, Y: 10.35000}, "x=10&y=10.35", apiVersion119},
		{dumb{T: 10, Y: 10.35000}, "y=10.35", nil},
		{dumb{Person: &person{Name: "gopher", Age: 4}}, "p=" + jsonPerson, nil},
		{nil, "", nil},
		{10, "", nil},
		{"not_a_struct", "", nil},
	}
	for _, tt := range tests {
		test := tt
		t.Run("", func(t *testing.T) {
			t.Parallel()
			got := queryString(test.input)
			if got != test.want {
				t.Errorf("queryString(%v). Want %q. Got %q.", test.input, test.want, got)
			}
			gotstring, gotAPI := queryStringVersion(test.input)
			if gotstring != test.want {
				t.Errorf("queryStringVersion(%v). Want %q. Got %q.", test.input, test.want, gotstring)
			}
			if gotAPI.compare(test.wantAPI) != 0 {
				t.Errorf("queryStringVersion(%v). Want API %q. Got API %q.", test.input, test.wantAPI, gotAPI)
			}
		})
	}
}

func TestAPIVersions(t *testing.T) {
	t.Parallel()
	tests := []struct {
		a                              string
		b                              string
		expectedALessThanB             bool
		expectedALessThanOrEqualToB    bool
		expectedAGreaterThanB          bool
		expectedAGreaterThanOrEqualToB bool
	}{
		{"1.11", "1.11", false, true, false, true},
		{"1.10", "1.11", true, true, false, false},
		{"1.11", "1.10", false, false, true, true},

		{"1.11-ubuntu0", "1.11", false, true, false, true},
		{"1.10", "1.11-el7", true, true, false, false},

		{"1.9", "1.11", true, true, false, false},
		{"1.11", "1.9", false, false, true, true},

		{"1.1.1", "1.1", false, false, true, true},
		{"1.1", "1.1.1", true, true, false, false},

		{"2.1", "1.1.1", false, false, true, true},
		{"2.1", "1.3.1", false, false, true, true},
		{"1.1.1", "2.1", true, true, false, false},
		{"1.3.1", "2.1", true, true, false, false},
	}

	for _, tt := range tests {
		test := tt
		t.Run(test.a+test.b, func(t *testing.T) {
			t.Parallel()
			a, _ := NewAPIVersion(test.a)
			b, _ := NewAPIVersion(test.b)

			if test.expectedALessThanB && !a.LessThan(b) {
				t.Errorf("Expected %#v < %#v", a, b)
			}
			if test.expectedALessThanOrEqualToB && !a.LessThanOrEqualTo(b) {
				t.Errorf("Expected %#v <= %#v", a, b)
			}
			if test.expectedAGreaterThanB && !a.GreaterThan(b) {
				t.Errorf("Expected %#v > %#v", a, b)
			}
			if test.expectedAGreaterThanOrEqualToB && !a.GreaterThanOrEqualTo(b) {
				t.Errorf("Expected %#v >= %#v", a, b)
			}
		})
	}
}

func TestPing(t *testing.T) {
	t.Parallel()
	fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
	client := newTestClient(fakeRT)
	err := client.Ping()
	if err != nil {
		t.Fatal(err)
	}
}

func TestPingFailing(t *testing.T) {
	t.Parallel()
	fakeRT := &FakeRoundTripper{message: "", status: http.StatusInternalServerError}
	client := newTestClient(fakeRT)
	err := client.Ping()
	if err == nil {
		t.Fatal("Expected non nil error, got nil")
	}
	expectedErrMsg := "API error (500): "
	if err.Error() != expectedErrMsg {
		t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error())
	}
}

func TestPingFailingWrongStatus(t *testing.T) {
	t.Parallel()
	fakeRT := &FakeRoundTripper{message: "", status: http.StatusAccepted}
	client := newTestClient(fakeRT)
	err := client.Ping()
	if err == nil {
		t.Fatal("Expected non nil error, got nil")
	}
	expectedErrMsg := "API error (202): "
	if err.Error() != expectedErrMsg {
		t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error())
	}
}

func TestPingErrorWithNativeClient(t *testing.T) {
	t.Parallel()
	srv, cleanup, err := newNativeServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte("aaaaaaaaaaa-invalid-aaaaaaaaaaa"))
	}))
	if err != nil {
		t.Fatal(err)
	}
	defer cleanup()
	srv.Start()
	defer srv.Close()
	endpoint := nativeBadEndpoint
	client, err := NewClient(endpoint)
	if err != nil {
		t.Fatal(err)
	}
	err = client.Ping()
	if err == nil {
		t.Fatal("Expected non nil error, got nil")
	}
}

func TestClientStreamTimeoutNotHit(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		for i := 0; i < 5; i++ {
			fmt.Fprintf(w, "%d\n", i)
			if f, ok := w.(http.Flusher); ok {
				f.Flush()
			}
			time.Sleep(100 * time.Millisecond)
		}
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w bytes.Buffer
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		setRawTerminal:    true,
		stdout:            &w,
		inactivityTimeout: 300 * time.Millisecond,
	})
	if err != nil {
		t.Fatal(err)
	}
	expected := "0\n1\n2\n3\n4\n"
	result := w.String()
	if result != expected {
		t.Fatalf("expected stream result %q, got: %q", expected, result)
	}
}

func TestClientStreamInactivityTimeout(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		for i := 0; i < 5; i++ {
			fmt.Fprintf(w, "%d\n", i)
			if f, ok := w.(http.Flusher); ok {
				f.Flush()
			}
			time.Sleep(500 * time.Millisecond)
		}
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w bytes.Buffer
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		setRawTerminal:    true,
		stdout:            &w,
		inactivityTimeout: 100 * time.Millisecond,
	})
	if err != ErrInactivityTimeout {
		t.Fatalf("expected request canceled error, got: %s", err)
	}
	expected := "0\n"
	result := w.String()
	if result != expected {
		t.Fatalf("expected stream result %q, got: %q", expected, result)
	}
}

func TestClientStreamContextDeadline(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprint(w, "abc\n")
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
		time.Sleep(time.Second)
		fmt.Fprint(w, "def\n")
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w bytes.Buffer
	ctx, cancel := context.WithTimeout(context.Background(), 400*time.Millisecond)
	defer cancel()
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		setRawTerminal: true,
		stdout:         &w,
		context:        ctx,
	})
	if err != context.DeadlineExceeded {
		t.Fatalf("expected %s, got: %s", context.DeadlineExceeded, err)
	}
	expected := "abc\n"
	result := w.String()
	if result != expected {
		t.Fatalf("expected stream result %q, got: %q", expected, result)
	}
}

func TestClientStreamContextCancel(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprint(w, "abc\n")
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
		time.Sleep(500 * time.Millisecond)
		fmt.Fprint(w, "def\n")
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w bytes.Buffer
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(200 * time.Millisecond)
		cancel()
	}()
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		setRawTerminal: true,
		stdout:         &w,
		context:        ctx,
	})
	if err != context.Canceled {
		t.Fatalf("expected %s, got: %s", context.Canceled, err)
	}
	expected := "abc\n"
	result := w.String()
	if result != expected {
		t.Fatalf("expected stream result %q, got: %q", expected, result)
	}
}

var mockPullOutput = `{"status":"Pulling from tsuru/static","id":"latest"}
{"status":"Already exists","progressDetail":{},"id":"a6aa3b66376f"}
{"status":"Pulling fs layer","progressDetail":{},"id":"106572778bf7"}
{"status":"Pulling fs layer","progressDetail":{},"id":"bac681833e51"}
{"status":"Pulling fs layer","progressDetail":{},"id":"7302e23ef08a"}
{"status":"Downloading","progressDetail":{"current":621,"total":621},"progress":"[==================================================\u003e]    621 B/621 B","id":"bac681833e51"}
{"status":"Verifying Checksum","progressDetail":{},"id":"bac681833e51"}
{"status":"Download complete","progressDetail":{},"id":"bac681833e51"}
{"status":"Downloading","progressDetail":{"current":1854,"total":1854},"progress":"[==================================================\u003e] 1.854 kB/1.854 kB","id":"106572778bf7"}
{"status":"Verifying Checksum","progressDetail":{},"id":"106572778bf7"}
{"status":"Download complete","progressDetail":{},"id":"106572778bf7"}
{"status":"Extracting","progressDetail":{"current":1854,"total":1854},"progress":"[==================================================\u003e] 1.854 kB/1.854 kB","id":"106572778bf7"}
{"status":"Extracting","progressDetail":{"current":1854,"total":1854},"progress":"[==================================================\u003e] 1.854 kB/1.854 kB","id":"106572778bf7"}
{"status":"Downloading","progressDetail":{"current":233019,"total":21059403},"progress":"[\u003e                                                  ]   233 kB/21.06 MB","id":"7302e23ef08a"}
{"status":"Downloading","progressDetail":{"current":462395,"total":21059403},"progress":"[=\u003e                                                 ] 462.4 kB/21.06 MB","id":"7302e23ef08a"}
{"status":"Downloading","progressDetail":{"current":8490555,"total":21059403},"progress":"[====================\u003e                              ] 8.491 MB/21.06 MB","id":"7302e23ef08a"}
{"status":"Downloading","progressDetail":{"current":20876859,"total":21059403},"progress":"[=================================================\u003e ] 20.88 MB/21.06 MB","id":"7302e23ef08a"}
{"status":"Verifying Checksum","progressDetail":{},"id":"7302e23ef08a"}
{"status":"Download complete","progressDetail":{},"id":"7302e23ef08a"}
{"status":"Pull complete","progressDetail":{},"id":"106572778bf7"}
{"status":"Extracting","progressDetail":{"current":621,"total":621},"progress":"[==================================================\u003e]    621 B/621 B","id":"bac681833e51"}
{"status":"Extracting","progressDetail":{"current":621,"total":621},"progress":"[==================================================\u003e]    621 B/621 B","id":"bac681833e51"}
{"status":"Pull complete","progressDetail":{},"id":"bac681833e51"}
{"status":"Extracting","progressDetail":{"current":229376,"total":21059403},"progress":"[\u003e                                                  ] 229.4 kB/21.06 MB","id":"7302e23ef08a"}
{"status":"Extracting","progressDetail":{"current":458752,"total":21059403},"progress":"[=\u003e                                                 ] 458.8 kB/21.06 MB","id":"7302e23ef08a"}
{"status":"Extracting","progressDetail":{"current":11239424,"total":21059403},"progress":"[==========================\u003e                        ] 11.24 MB/21.06 MB","id":"7302e23ef08a"}
{"status":"Extracting","progressDetail":{"current":21059403,"total":21059403},"progress":"[==================================================\u003e] 21.06 MB/21.06 MB","id":"7302e23ef08a"}
{"status":"Pull complete","progressDetail":{},"id":"7302e23ef08a"}
{"status":"Digest: sha256:b754472891aa7e33fc0214e3efa988174f2c2289285fcae868b7ec8b6675fc77"}
{"status":"Status: Downloaded newer image for 192.168.50.4:5000/tsuru/static"}
`

func TestClientStreamJSONDecode(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte(mockPullOutput))
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w bytes.Buffer
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		stdout:         &w,
		useJSONDecoder: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	expected := `latest: Pulling from tsuru/static
a6aa3b66376f: Already exists
106572778bf7: Pulling fs layer
bac681833e51: Pulling fs layer
7302e23ef08a: Pulling fs layer
bac681833e51: Verifying Checksum
bac681833e51: Download complete
106572778bf7: Verifying Checksum
106572778bf7: Download complete
7302e23ef08a: Verifying Checksum
7302e23ef08a: Download complete
106572778bf7: Pull complete
bac681833e51: Pull complete
7302e23ef08a: Pull complete
Digest: sha256:b754472891aa7e33fc0214e3efa988174f2c2289285fcae868b7ec8b6675fc77
Status: Downloaded newer image for 192.168.50.4:5000/tsuru/static
`
	result := w.String()
	if result != expected {
		t.Fatalf("expected stream result %q, got: %q", expected, result)
	}
}

type terminalBuffer struct {
	bytes.Buffer
}

func (b *terminalBuffer) FD() uintptr {
	return os.Stdout.Fd()
}

func (b *terminalBuffer) IsTerminal() bool {
	return true
}

func TestClientStreamJSONDecodeWithTerminal(t *testing.T) {
	if !terminal.IsTerminal(int(os.Stdout.Fd())) {
		t.Skip("requires a terminal")
	}
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte(mockPullOutput))
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w terminalBuffer
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		stdout:         &w,
		useJSONDecoder: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	const expected = "latest: Pulling from tsuru/static\n\n" +
		"\x1b[1A\x1b[2K\ra6aa3b66376f: Already exists \r\x1b[1B\n" +
		"\x1b[1A\x1b[2K\r106572778bf7: Pulling fs layer \r\x1b[1B\n" +
		"\x1b[1A\x1b[2K\rbac681833e51: Pulling fs layer \r\x1b[1B\n" +
		"\x1b[1A\x1b[2K\r7302e23ef08a: Pulling fs layer \r\x1b[1B\x1b[2A\x1b[2K\rbac681833e51: Downloading [==================================================>]     621B/621B\r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Verifying Checksum \r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Download complete \r\x1b[2B\x1b[3A\x1b[2K\r106572778bf7: Downloading [==================================================>]  1.854kB/1.854kB\r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Verifying Checksum \r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Download complete \r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Extracting [==================================================>]  1.854kB/1.854kB\r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Extracting [==================================================>]  1.854kB/1.854kB\r\x1b[3B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [>                                                  ]    233kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [=>                                                 ]  462.4kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [====================>                              ]  8.491MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [=================================================> ]  20.88MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Verifying Checksum \r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Download complete \r\x1b[1B\x1b[3A\x1b[2K\r106572778bf7: Pull complete \r\x1b[3B\x1b[2A\x1b[2K\rbac681833e51: Extracting [==================================================>]     621B/621B\r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Extracting [==================================================>]     621B/621B\r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Pull complete \r\x1b[2B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [>                                                  ]  229.4kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [=>                                                 ]  458.8kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [==========================>                        ]  11.24MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [==================================================>]  21.06MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Pull complete \r\x1b[1BDigest: sha256:b754472891aa7e33fc0214e3efa988174f2c2289285fcae868b7ec8b6675fc77\n" +
		"Status: Downloaded newer image for 192.168.50.4:5000/tsuru/static\n"
	result := w.String()
	if result != expected {
		t.Fatalf("wrong stream result\nwant %q\ngot:  %q", expected, result)
	}
}

func TestClientDoContextDeadline(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		time.Sleep(500 * time.Millisecond)
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()
	_, err = client.do(http.MethodPost, "/image/create", doOptions{
		context: ctx,
	})
	if err != context.DeadlineExceeded {
		t.Fatalf("expected %s, got: %s", context.DeadlineExceeded, err)
	}
}

func TestClientDoContextCancel(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
		time.Sleep(500 * time.Millisecond)
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(100 * time.Millisecond)
		cancel()
	}()
	_, err = client.do(http.MethodPost, "/image/create", doOptions{
		context: ctx,
	})
	if err != context.Canceled {
		t.Fatalf("expected %s, got: %s", context.Canceled, err)
	}
}

func TestClientStreamTimeoutNativeClient(t *testing.T) {
	t.Parallel()
	srv, cleanup, err := newNativeServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		for i := 0; i < 5; i++ {
			fmt.Fprintf(w, "%d\n", i)
			if f, ok := w.(http.Flusher); ok {
				f.Flush()
			}
			time.Sleep(500 * time.Millisecond)
		}
	}))
	if err != nil {
		t.Fatal(err)
	}
	defer cleanup()
	srv.Start()
	defer srv.Close()
	client, err := NewClient(nativeProtocol + "://" + srv.Listener.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	var w bytes.Buffer
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		setRawTerminal:    true,
		stdout:            &w,
		inactivityTimeout: 50 * time.Millisecond,
	})
	if err != ErrInactivityTimeout {
		t.Fatalf("expected request canceled error, got: %s", err)
	}
	expected := "0\n"
	result := w.String()
	if result != expected {
		t.Fatalf("expected stream result %q, got: %q", expected, result)
	}
}

func TestClientStreamJSONDecoderEOFOutputWriter(t *testing.T) {
	t.Parallel()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprint(w, "{}")
		time.Sleep(500 * time.Millisecond)
	}))
	defer srv.Close()
	client, err := NewClient(srv.URL)
	if err != nil {
		t.Fatal(err)
	}
	var w eofWriter
	err = client.stream(http.MethodPost, "/image/create", streamOptions{
		setRawTerminal: true,
		useJSONDecoder: true,
		stdout:         &w,
	})
	if err != nil {
		t.Fatal(err)
	}
}

type eofWriter struct{}

func (w eofWriter) Write(b []byte) (int, error) { return len(b), io.EOF }

type FakeRoundTripper struct {
	message  string
	status   int
	header   map[string]string
	requests []*http.Request
}

func (rt *FakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	body := strings.NewReader(rt.message)
	rt.requests = append(rt.requests, r)
	res := &http.Response{
		StatusCode: rt.status,
		Body:       ioutil.NopCloser(body),
		Header:     make(http.Header),
	}
	for k, v := range rt.header {
		res.Header.Set(k, v)
	}
	return res, nil
}

func (rt *FakeRoundTripper) Reset() {
	rt.requests = nil
}

type person struct {
	Name string
	Age  int `json:"age"`
}

type dumb struct {
	T      int `qs:"-"`
	v      int
	W      float32 `ver:"1.24"`
	X      int     `ver:"1.19"`
	Y      float64
	Z      int     `qs:"zee"`
	Person *person `qs:"p"`
}
