// Copyright 2010 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 http

import (
	"bytes"
	"io/ioutil"
	"strings"
	"testing"
)

type respWriteTest struct {
	Resp Response
	Raw  string
}

func TestResponseWrite(t *testing.T) {
	respWriteTests := []respWriteTest{
		// HTTP/1.0, identity coding; no trailer
		{
			Response{
				StatusCode: 503,
				Protocol: Protocol{
					Name:  "HTTP/1.0",
					Major: 1,
					Minor: 0,
				},
				Request:       dummyReq("GET"),
				Header:        Header{},
				Body:          ioutil.NopCloser(strings.NewReader("abcdef")),
				ContentLength: 6,
			},

			"HTTP/1.0 503 Service Unavailable\r\n" +
				"Content-Length: 6\r\n\r\n" +
				"abcdef",
		},
		// Unchunked response without Content-Length.
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.0",
					Major: 1,
					Minor: 0,
				},
				Request:       dummyReq("GET"),
				Header:        Header{},
				Body:          ioutil.NopCloser(strings.NewReader("abcdef")),
				ContentLength: -1,
			},
			"HTTP/1.0 200 OK\r\n" +
				"\r\n" +
				"abcdef",
		},
		// HTTP/1.1 response with unknown length and Connection: close
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:       dummyReq("GET"),
				Header:        Header{},
				Body:          ioutil.NopCloser(strings.NewReader("abcdef")),
				ContentLength: -1,
				Close:         true,
			},
			"HTTP/1.1 200 OK\r\n" +
				"Connection: close\r\n" +
				"\r\n" +
				"abcdef",
		},
		// HTTP/1.1 response with unknown length and not setting connection: close
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:       dummyReq11("GET"),
				Header:        Header{},
				Body:          ioutil.NopCloser(strings.NewReader("abcdef")),
				ContentLength: -1,
				Close:         false,
			},
			"HTTP/1.1 200 OK\r\n" +
				"Connection: close\r\n" +
				"\r\n" +
				"abcdef",
		},
		// HTTP/1.1 response with unknown length and not setting connection: close, but
		// setting chunked.
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:          dummyReq11("GET"),
				Header:           Header{},
				Body:             ioutil.NopCloser(strings.NewReader("abcdef")),
				ContentLength:    -1,
				TransferEncoding: []string{"chunked"},
				Close:            false,
			},
			"HTTP/1.1 200 OK\r\n" +
				"Transfer-Encoding: chunked\r\n\r\n" +
				"6\r\nabcdef\r\n0\r\n\r\n",
		},
		// HTTP/1.1 response 0 content-length, and nil body
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:       dummyReq11("GET"),
				Header:        Header{},
				Body:          nil,
				ContentLength: 0,
				Close:         false,
			},
			"HTTP/1.1 200 OK\r\n" +
				"Content-Length: 0\r\n" +
				"\r\n",
		},
		// HTTP/1.1 response 0 content-length, and non-nil empty body
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:       dummyReq11("GET"),
				Header:        Header{},
				Body:          ioutil.NopCloser(strings.NewReader("")),
				ContentLength: 0,
				Close:         false,
			},
			"HTTP/1.1 200 OK\r\n" +
				"Content-Length: 0\r\n" +
				"\r\n",
		},
		// HTTP/1.1 response 0 content-length, and non-nil non-empty body
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:       dummyReq11("GET"),
				Header:        Header{},
				Body:          ioutil.NopCloser(strings.NewReader("foo")),
				ContentLength: 0,
				Close:         false,
			},
			"HTTP/1.1 200 OK\r\n" +
				"Connection: close\r\n" +
				"\r\nfoo",
		},
		// HTTP/1.1, chunked coding; empty trailer; close
		{
			Response{
				StatusCode: 200,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:          dummyReq("GET"),
				Header:           Header{},
				Body:             ioutil.NopCloser(strings.NewReader("abcdef")),
				ContentLength:    6,
				TransferEncoding: []string{"chunked"},
				Close:            true,
			},

			"HTTP/1.1 200 OK\r\n" +
				"Connection: close\r\n" +
				"Transfer-Encoding: chunked\r\n\r\n" +
				"6\r\nabcdef\r\n0\r\n\r\n",
		},

		// Header value with a newline character (Issue 914).
		// Also tests removal of leading and trailing whitespace.
		{
			Response{
				StatusCode: 204,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request: dummyReq("GET"),
				Header: Header{
					"Foo": []string{" Bar\nBaz "},
				},
				Body:             nil,
				ContentLength:    0,
				TransferEncoding: []string{"chunked"},
				Close:            true,
			},

			"HTTP/1.1 204 No Content\r\n" +
				"Connection: close\r\n" +
				"Foo: Bar Baz\r\n" +
				"\r\n",
		},

		// Want a single Content-Length header. Fixing issue 8180 where
		// there were two.
		{
			Response{
				StatusCode: StatusOK,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:          &Request{Method: "POST"},
				Header:           Header{},
				ContentLength:    0,
				TransferEncoding: nil,
				Body:             nil,
			},
			"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n",
		},

		// When a response to a POST has Content-Length: -1, make sure we don't
		// write the Content-Length as -1.
		{
			Response{
				StatusCode: StatusOK,
				Protocol: Protocol{
					Name:  "HTTP/1.1",
					Major: 1,
					Minor: 1,
				},
				Request:       &Request{Method: "POST"},
				Header:        Header{},
				ContentLength: -1,
				Body:          ioutil.NopCloser(strings.NewReader("abcdef")),
			},
			"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nabcdef",
		},

		// Status code under 100 should be zero-padded to
		// three digits.  Still bogus, but less bogus. (be
		// consistent with generating three digits, since the
		// Transport requires it)
		{
			Response{
				StatusCode: 7,
				Status:     "license to violate specs",
				Protocol: Protocol{
					Name:  "HTTP/1.0",
					Major: 1,
					Minor: 0,
				},
				Request: dummyReq("GET"),
				Header:  Header{},
				Body:    nil,
			},

			"HTTP/1.0 007 license to violate specs\r\nContent-Length: 0\r\n\r\n",
		},

		// No stutter.  Status code in 1xx range response should
		// not include a Content-Length header.  See issue #16942.
		{
			Response{
				StatusCode: 123,
				Status:     "123 Sesame Street",
				Protocol: Protocol{
					Name:  "HTTP/1.0",
					Major: 1,
					Minor: 0,
				},
				Request: dummyReq("GET"),
				Header:  Header{},
				Body:    nil,
			},

			"HTTP/1.0 123 Sesame Street\r\n\r\n",
		},

		// Status code 204 (No content) response should not include a
		// Content-Length header.  See issue #16942.
		{
			Response{
				StatusCode: 204,
				Status:     "No Content",
				Protocol: Protocol{
					Name:  "HTTP/1.0",
					Major: 1,
					Minor: 0,
				},
				Request: dummyReq("GET"),
				Header:  Header{},
				Body:    nil,
			},

			"HTTP/1.0 204 No Content\r\n\r\n",
		},
	}

	for i := range respWriteTests {
		tt := &respWriteTests[i]
		var braw bytes.Buffer
		err := tt.Resp.Write(&braw)
		if err != nil {
			t.Errorf("error writing #%d: %s", i, err)
			continue
		}
		sraw := braw.String()
		if sraw != tt.Raw {
			t.Errorf("Test %d, expecting:\n%q\nGot:\n%q\n", i, tt.Raw, sraw)
			continue
		}
	}
}
