package auth

import (
	"bytes"
	"context"
	"fmt"
	"mime/multipart"
	"net/http"
	"net/url"
	"testing"
	"time"

	"go-common/library/ecode"
	"go-common/library/log"
	bm "go-common/library/net/http/blademaster"
	"go-common/library/net/metadata"
	"go-common/library/net/netutil/breaker"
	"go-common/library/net/rpc/warden"
	xtime "go-common/library/time"

	"github.com/stretchr/testify/assert"
)

const (
	_testUID = "2231365"
)

type Response struct {
	Code int    `json:"code"`
	Data string `json:"data"`
}

func init() {
	log.Init(&log.Config{
		Stdout: true,
	})
}

func client() *bm.Client {
	return bm.NewClient(&bm.ClientConfig{
		App: &bm.App{
			Key:    "53e2fa226f5ad348",
			Secret: "3cf6bd1b0ff671021da5f424fea4b04a",
		},
		Dial:      xtime.Duration(time.Second),
		Timeout:   xtime.Duration(time.Second),
		KeepAlive: xtime.Duration(time.Second * 10),
		Breaker: &breaker.Config{
			Window:  xtime.Duration(time.Second),
			Sleep:   xtime.Duration(time.Millisecond * 100),
			Bucket:  10,
			Ratio:   0.5,
			Request: 100,
		},
	})
}

func create() *Auth {
	return New(&Config{
		Identify:    &warden.ClientConfig{},
		DisableCSRF: false,
	})
}

func engine() *bm.Engine {
	e := bm.NewServer(nil)
	authn := create()
	e.GET("/user", authn.User, func(ctx *bm.Context) {
		mid, _ := ctx.Get("mid")
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/metadata/user", authn.User, func(ctx *bm.Context) {
		mid := metadata.Value(ctx, metadata.Mid)
		ctx.JSON(fmt.Sprintf("%d", mid.(int64)), nil)
	})
	e.GET("/mobile", authn.UserMobile, func(ctx *bm.Context) {
		mid, _ := ctx.Get("mid")
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/metadata/mobile", authn.UserMobile, func(ctx *bm.Context) {
		mid := metadata.Value(ctx, metadata.Mid)
		ctx.JSON(fmt.Sprintf("%d", mid.(int64)), nil)
	})
	e.GET("/web", authn.UserWeb, func(ctx *bm.Context) {
		mid, _ := ctx.Get("mid")
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/guest", authn.Guest, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/guest/web", authn.GuestWeb, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/guest/mobile", authn.GuestMobile, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.POST("/guest/csrf", authn.Guest, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	return e
}

func TestFromNilConfig(t *testing.T) {
	New(nil)
}

func TestIdentifyHandler(t *testing.T) {
	e := engine()
	go e.Run(":18080")

	time.Sleep(time.Second)

	testWebUser(t, "/user")
	testWebUser(t, "/metadata/user")
	testWebUser(t, "/web")
	testWebUser(t, "/guest")
	testWebUser(t, "/guest/web")
	testWebUserFailed(t, "/user")
	testWebUserFailed(t, "/web")

	testMobileUser(t, "/user")
	testMobileUser(t, "/mobile")
	testMobileUser(t, "/metadata/mobile")
	testMobileUser(t, "/guest")
	testMobileUser(t, "/guest/mobile")
	testMobileUserFailed(t, "/user")
	testMobileUserFailed(t, "/mobile")

	testGuest(t, "/guest")
	testGuestCSRF(t, "/guest/csrf")
	testGuestCSRFFailed(t, "/guest/csrf")
	testMultipartCSRF(t, "/guest/csrf")

	if err := e.Server().Shutdown(context.TODO()); err != nil {
		t.Logf("Failed to shutdown bm engine: %v", err)
	}
}

func testWebUser(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http:
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}

func testMobileUser(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	query.Set("access_key", "cdbd166be6673a5a4f6fbcdd88569edf")
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http:
	assert.NoError(t, err)

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}

func testWebUserFailed(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http:
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "53c4b106fb4462f1",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "6eeda532%2C1515837495%2C5a6baa4e",
	})

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, ecode.NoLogin.Code(), res.Code)
	assert.Empty(t, res.Data)
}

func testMobileUserFailed(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	query.Set("access_key", "5dce488c2ff8d62d7b131da40ae18729")
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http:
	assert.NoError(t, err)

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, ecode.NoLogin.Code(), res.Code)
	assert.Empty(t, res.Data)
}

func testGuest(t *testing.T, path string) {
	res := Response{}
	cli := client()
	req, err := http.NewRequest(http.MethodGet, "http:
	assert.NoError(t, err)

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, "0", res.Data)
}

func testGuestCSRF(t *testing.T, path string) {
	res := Response{}
	param := url.Values{}
	param.Set("csrf", "c1524bbf3aa5a1996ff7b1f29a09e796")
	cli := client()
	req, err := cli.NewRequest(http.MethodPost, "http:
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})
	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}

func testGuestCSRFFailed(t *testing.T, path string) {
	res := Response{}
	param := url.Values{}
	param.Set("csrf", "invalid-csrf-token")
	cli := client()
	req, err := cli.NewRequest(http.MethodPost, "http:
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})
	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, ecode.CsrfNotMatchErr.Code(), res.Code)
	assert.Empty(t, res.Data)
}

func testMultipartCSRF(t *testing.T, path string) {
	res := Response{}
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	writer.WriteField("csrf", "c1524bbf3aa5a1996ff7b1f29a09e796")
	writer.Close()
	req, err := http.NewRequest("POST", "http:
	assert.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	cli := client()
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})
	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)
	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}
package auth

import (
	"bytes"
	"context"
	"fmt"
	"mime/multipart"
	"net/http"
	"net/url"
	"testing"
	"time"

	"go-common/library/ecode"
	"go-common/library/log"
	bm "go-common/library/net/http/blademaster"
	"go-common/library/net/metadata"
	"go-common/library/net/netutil/breaker"
	"go-common/library/net/rpc/warden"
	xtime "go-common/library/time"

	"github.com/stretchr/testify/assert"
)

const (
	_testUID = "2231365"
)

type Response struct {
	Code int    `json:"code"`
	Data string `json:"data"`
}

func init() {
	log.Init(&log.Config{
		Stdout: true,
	})
}

func client() *bm.Client {
	return bm.NewClient(&bm.ClientConfig{
		App: &bm.App{
			Key:    "53e2fa226f5ad348",
			Secret: "3cf6bd1b0ff671021da5f424fea4b04a",
		},
		Dial:      xtime.Duration(time.Second),
		Timeout:   xtime.Duration(time.Second),
		KeepAlive: xtime.Duration(time.Second * 10),
		Breaker: &breaker.Config{
			Window:  xtime.Duration(time.Second),
			Sleep:   xtime.Duration(time.Millisecond * 100),
			Bucket:  10,
			Ratio:   0.5,
			Request: 100,
		},
	})
}

func create() *Auth {
	return New(&Config{
		Identify:    &warden.ClientConfig{},
		DisableCSRF: false,
	})
}

func engine() *bm.Engine {
	e := bm.NewServer(nil)
	authn := create()
	e.GET("/user", authn.User, func(ctx *bm.Context) {
		mid, _ := ctx.Get("mid")
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/metadata/user", authn.User, func(ctx *bm.Context) {
		mid := metadata.Value(ctx, metadata.Mid)
		ctx.JSON(fmt.Sprintf("%d", mid.(int64)), nil)
	})
	e.GET("/mobile", authn.UserMobile, func(ctx *bm.Context) {
		mid, _ := ctx.Get("mid")
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/metadata/mobile", authn.UserMobile, func(ctx *bm.Context) {
		mid := metadata.Value(ctx, metadata.Mid)
		ctx.JSON(fmt.Sprintf("%d", mid.(int64)), nil)
	})
	e.GET("/web", authn.UserWeb, func(ctx *bm.Context) {
		mid, _ := ctx.Get("mid")
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/guest", authn.Guest, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/guest/web", authn.GuestWeb, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.GET("/guest/mobile", authn.GuestMobile, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	e.POST("/guest/csrf", authn.Guest, func(ctx *bm.Context) {
		var (
			mid int64
		)
		if _mid, ok := ctx.Get("mid"); ok {
			mid, _ = _mid.(int64)
		}
		ctx.JSON(fmt.Sprintf("%d", mid), nil)
	})
	return e
}

func TestFromNilConfig(t *testing.T) {
	New(nil)
}

func TestIdentifyHandler(t *testing.T) {
	e := engine()
	go e.Run(":18080")

	time.Sleep(time.Second)

	// test cases
	testWebUser(t, "/user")
	testWebUser(t, "/metadata/user")
	testWebUser(t, "/web")
	testWebUser(t, "/guest")
	testWebUser(t, "/guest/web")
	testWebUserFailed(t, "/user")
	testWebUserFailed(t, "/web")

	testMobileUser(t, "/user")
	testMobileUser(t, "/mobile")
	testMobileUser(t, "/metadata/mobile")
	testMobileUser(t, "/guest")
	testMobileUser(t, "/guest/mobile")
	testMobileUserFailed(t, "/user")
	testMobileUserFailed(t, "/mobile")

	testGuest(t, "/guest")
	testGuestCSRF(t, "/guest/csrf")
	testGuestCSRFFailed(t, "/guest/csrf")
	testMultipartCSRF(t, "/guest/csrf")

	if err := e.Server().Shutdown(context.TODO()); err != nil {
		t.Logf("Failed to shutdown bm engine: %v", err)
	}
}

func testWebUser(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http://127.0.0.1:18080/"+path, "", query)
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}

func testMobileUser(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	query.Set("access_key", "cdbd166be6673a5a4f6fbcdd88569edf")
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http://127.0.0.1:18080"+path, "", query)
	assert.NoError(t, err)

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}

func testWebUserFailed(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http://127.0.0.1:18080/"+path, "", query)
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "53c4b106fb4462f1",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "6eeda532%2C1515837495%2C5a6baa4e",
	})

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, ecode.NoLogin.Code(), res.Code)
	assert.Empty(t, res.Data)
}

func testMobileUserFailed(t *testing.T, path string) {
	res := Response{}
	query := url.Values{}
	query.Set("access_key", "5dce488c2ff8d62d7b131da40ae18729")
	cli := client()
	req, err := cli.NewRequest(http.MethodGet, "http://127.0.0.1:18080"+path, "", query)
	assert.NoError(t, err)

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, ecode.NoLogin.Code(), res.Code)
	assert.Empty(t, res.Data)
}

func testGuest(t *testing.T, path string) {
	res := Response{}
	cli := client()
	req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:18080"+path, nil)
	assert.NoError(t, err)

	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, "0", res.Data)
}

func testGuestCSRF(t *testing.T, path string) {
	res := Response{}
	param := url.Values{}
	param.Set("csrf", "c1524bbf3aa5a1996ff7b1f29a09e796")
	cli := client()
	req, err := cli.NewRequest(http.MethodPost, "http://127.0.0.1:18080"+path, "", param)
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})
	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}

func testGuestCSRFFailed(t *testing.T, path string) {
	res := Response{}
	param := url.Values{}
	param.Set("csrf", "invalid-csrf-token")
	cli := client()
	req, err := cli.NewRequest(http.MethodPost, "http://127.0.0.1:18080"+path, "", param)
	assert.NoError(t, err)

	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})
	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)

	assert.Equal(t, ecode.CsrfNotMatchErr.Code(), res.Code)
	assert.Empty(t, res.Data)
}

func testMultipartCSRF(t *testing.T, path string) {
	res := Response{}
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	writer.WriteField("csrf", "c1524bbf3aa5a1996ff7b1f29a09e796")
	writer.Close()
	req, err := http.NewRequest("POST", "http://127.0.0.1:18080"+path, body)
	assert.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	cli := client()
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID",
		Value: _testUID,
	})
	req.AddCookie(&http.Cookie{
		Name:  "DedeUserID__ckMd5",
		Value: "36976f7a5cb6e4a6",
	})
	req.AddCookie(&http.Cookie{
		Name:  "SESSDATA",
		Value: "7bf20cf0%2C1540627371%2C8ec39f0c",
	})
	err = cli.Do(context.TODO(), req, &res)
	assert.NoError(t, err)
	assert.Equal(t, 0, res.Code)
	assert.Equal(t, _testUID, res.Data)
}
