package resource

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/pingcap/test-infra/services/tps/pkg/common"

	commM "github.com/pingcap/test-infra/common/model"
	rmsRespM "github.com/pingcap/test-infra/common/model/rms/response"
	"github.com/stretchr/testify/assert"
)

// test NewCmdlineResourcePoolMgr

func getSampleMeta() []PoolMeta {
	return []PoolMeta{
		{alias: "default", id: 123, name: "default"},
		{alias: "nonexistent", id: 456, name: "nonexistent"},
		{alias: "", id: 123, name: "default"},
	}
}

func getChangedMeta() []PoolMeta {
	return []PoolMeta{
		{alias: "benchbot", id: 789, name: "benchbot"},
		{alias: "default", id: 123, name: "default"},
		{alias: "", id: 789, name: "benchbot"},
	}
}

type tcmsStub struct {
	rt  []*commM.ResourcePool
	err error
}

func (t tcmsStub) ListResourcePools() ([]*commM.ResourcePool, error) {
	return t.rt, t.err
}

func TestTcmsPoolMetaProvider(t *testing.T) {
	stub := tcmsStub{rt: []*commM.ResourcePool{
		{Name: "p1", Aliases: "p1"},
		{Name: "p2", Aliases: "p2"},
	}}
	provider := TcmsPoolMetaProvider{tcmsClient: stub}
	metaList, err := provider.Get()
	assert.NoError(t, err)
	assert.Equal(t, 2, len(metaList))
	stub.rt[1].Aliases = "p1"
	_, err = provider.Get()
	assert.Error(t, err)
	stub.rt[1].Aliases = ""
	metaList, err = provider.Get()
	assert.NoError(t, err)
	assert.Equal(t, "p2", metaList[1].alias)
}

// test ResourcePoolMgr

func TestResourcePool_SetRespool4PlanExec(t *testing.T) {
	exec := &commM.TestPlanExecution{}

	pools := ResourcePoolMgr{
		pools: []ResourcePoolInterface{&ResourcePool{
			id:   123,
			name: "default",
			rms:  rms{url: "http://rms", httpClient: http.DefaultClient},
		}, &ResourcePool{}},
		aliasDict:      map[string]commM.ID{"": 123, "default": 123, "benchbot": 456},
		identifierDict: map[commM.ID]int{123: 0, 456: 1},
	}
	_, err := pools.SetRespool4PlanExec(exec, &Testbed{inner: &commM.TestBed{
		ResourcePool: "default",
	}})
	assert.NoError(t, err)
	assert.Equal(t, commM.ID(123), exec.ResourcepoolID)
}

func TestResourcePoolMgr_GetResourcePool(t *testing.T) {
	pool := &ResourcePool{}
	pools := ResourcePoolMgr{
		pools:          []ResourcePoolInterface{pool},
		aliasDict:      map[string]commM.ID{"": 123, "default": 123},
		identifierDict: map[commM.ID]int{123: 0},
	}
	assert.Nil(t, pools.GetResourcePoolByAlias("non-exist"), "non exist should return nil")
	assert.Equal(t, pool, pools.GetResourcePoolByAlias("default"), "existed pool should return")
	assert.Equal(t, pools.GetResourcePoolByAlias(""), pools.GetResourcePoolByAlias("default"), "empty resource pool should return default pool")
}

func TestResourcePoolMgr_RefreshPoolMeta(t *testing.T) {
	mgr := ResourcePoolMgr{poolMetaProvider: CmdlinePoolMetaProvider{rmsUrl: "http://rms", pools: []string{"default", "reserved"}}}
	mgr.init()
	err := mgr.refreshPoolMeta()
	assert.NoError(t, err)
	expected := ResourcePoolMgr{
		pools:             mgr.pools,
		nonAvailablePools: map[ResourcePoolInterface]struct{}{},
		aliasDict: map[string]commM.ID{
			"":         0,
			"default":  0,
			"reserved": 1,
		},
		identifierDict: map[commM.ID]int{
			0: 0,
			1: 1,
		},
		poolMetaProvider: CmdlinePoolMetaProvider{rmsUrl: "http://rms",
			pools: []string{"default", "reserved"},
		},
	}
	assert.Equal(t, expected, mgr)
	pool := &ResourcePool{name: "default", rms: rms{url: "http://rms", httpClient: http.DefaultClient}}
	assert.Equal(t, pool.rms, rms{url: "http://rms", httpClient: http.DefaultClient})
}

func TestResourcePoolMgr_Merge(t *testing.T) {
	mgr := ResourcePoolMgr{}
	mgr.init()
	mgr.Merge(getSampleMeta())
	assert.Equal(t, 2, len(mgr.pools))
	assert.Equal(t, commM.ID(123), mgr.GetResourcePoolByAlias("").ID())
	assert.Equal(t, commM.ID(123), mgr.GetResourcePoolByAlias("default").ID())
	oldPools := mgr.pools
	mgr.Merge(getChangedMeta())
	for _, oldPool := range oldPools {
		for _, newPool := range mgr.pools {
			if oldPool.ID() == newPool.ID() {
				assert.True(t, oldPool == newPool)
			}
		}
	}
	assert.True(t, mgr.pools[1].Name() == "benchbot")
	assert.Equal(t, "benchbot", mgr.GetResourcePoolByAlias("").Name())
	assert.Equal(t, 2, len(mgr.pools))
}

type resourcePoolStub struct {
	refreshCalled int
	refreshError  error
	assumed       int
	retString     string
	checkStatus   ResCheckStatus
}

func (r resourcePoolStub) String() string {
	return r.retString
}

func (r *resourcePoolStub) Refresh(context.Context) error {
	r.refreshCalled++
	return r.refreshError
}

func (r *resourcePoolStub) CheckResourceEnough(request PhysicalResource) ResCheckStatus {
	if r.checkStatus.Status != ResCheckStatusUnknown {
		return r.checkStatus
	}
	if r.assumed == 0 {
		return ResCheckEnough
	} else {
		return ResCheckStatus{ResCheckStatusInsufficient, "Insufficient Resource"}
	}
}

func (r *resourcePoolStub) Assume(ctx JobContext, testbedName string, request PhysicalResource) error {
	r.assumed++
	return nil
}

func (r resourcePoolStub) Copy() ResourcePoolInterface {
	return &r
}

func (r *resourcePoolStub) ID() commM.ID {
	return 123
}

func (r *resourcePoolStub) Name() string {
	return "test"
}

func (r *resourcePoolStub) RMS() RmsInterface {
	return rms{
		url:        "http://rms",
		httpClient: http.DefaultClient,
	}
}

func TestResourcePoolMgr_Copy(t *testing.T) {
	stub := &resourcePoolStub{}
	mgr := ResourcePoolMgr{pools: []ResourcePoolInterface{stub}}
	mgr.init()
	copiedMgr := mgr.Copy()
	assert.Equal(t, 0, mgr.pools[0].(*resourcePoolStub).assumed)
	copiedMgr.pools[0].(*resourcePoolStub).assumed = 1
	assert.Equal(t, 0, mgr.pools[0].(*resourcePoolStub).assumed)
}

func GetSimpleJobCtx(id int) JobContext {
	return NewJobContext(context.TODO(), &common.ArgoJob{PlanExec: &commM.TestPlanExecution{ID: commM.ID(id)}})
}

func TestResourcePoolMgr_IsResourceEnough(t *testing.T) {
	mgr := ResourcePoolMgr{poolMetaProvider: CmdlinePoolMetaProvider{rmsUrl: "http://rms", pools: []string{"default"}}}
	mgr.init()
	err := mgr.refreshPoolMeta()
	stub := &resourcePoolStub{}
	mgr.pools[0] = stub
	assert.NoError(t, err)
	beds := toITestbeds(
		&commM.TestBed{RmsUrl: "http://rms", ResourcePool: "default"},
		&commM.TestBed{RmsUrl: "http://rms", ResourcePool: "default"},
	)
	twobeds := mgr.CheckResourceEnough(GetSimpleJobCtx(1), beds)
	assert.Equal(t, ResCheckStatus{ResCheckStatusInsufficient, "Insufficient Resource"}, twobeds)
	singlebeds := mgr.CheckResourceEnough(GetSimpleJobCtx(1), beds[:1])
	assert.Equal(t, ResCheckEnough, singlebeds)
	statusExCapacity := ResCheckStatus{ResCheckStatusError, "ExCapacity"}
	stub.checkStatus = statusExCapacity
	twobeds = mgr.CheckResourceEnough(GetSimpleJobCtx(1), beds)
	assert.Equal(t, statusExCapacity, twobeds)
	singlebeds = mgr.CheckResourceEnough(GetSimpleJobCtx(1), beds[:1])
	assert.Equal(t, statusExCapacity, singlebeds)
	statusDown := ResCheckStatus{Status: ResCheckStatusTempDown}
	stub.checkStatus = statusDown
	down := mgr.CheckResourceEnough(GetSimpleJobCtx(1), beds[:1])
	assert.Equal(t, statusDown, down)
	mgr.nonAvailablePools[stub] = struct{}{}
	nonAvailable := mgr.CheckResourceEnough(GetSimpleJobCtx(1), beds[:1])
	assert.Equal(t, ResCheckStatusError, nonAvailable.Status)
	directPass := mgr.CheckResourceEnough(GetSimpleJobCtx(1), nil)
	assert.Equal(t, ResCheckEnough, directPass)
}

func TestResourcePoolMgr_refreshEveryPools(t *testing.T) {
	mgr := ResourcePoolMgr{poolMetaProvider: CmdlinePoolMetaProvider{rmsUrl: "http://rms", pools: []string{"default", "reserved"}}}
	mgr.init()
	err := mgr.refreshPoolMeta()
	assert.NoError(t, err)

	stub0 := &resourcePoolStub{}
	stub1 := &resourcePoolStub{}

	mgr.pools[0] = stub0
	mgr.pools[1] = stub1
	err = mgr.refreshEveryPool(context.TODO())
	assert.NoError(t, err)
	assert.Equal(t, 1, stub0.refreshCalled)
}

func TestResourcePoolMgr_String(t *testing.T) {
	mgr := ResourcePoolMgr{poolMetaProvider: CmdlinePoolMetaProvider{rmsUrl: "http://rms", pools: []string{"default", "reserved"}}}
	mgr.init()
	err := mgr.refreshPoolMeta()
	assert.NoError(t, err)
	stub0 := &resourcePoolStub{}
	stub1 := &resourcePoolStub{}
	mgr.pools[0] = stub0
	mgr.pools[1] = stub1
	stub0.retString = fmt.Sprintf(`{"poolID":"%s"}`, stub0.ID())
	stub1.retString = `{}`
	stub1.refreshError = fmt.Errorf("testerror")
	err = mgr.refreshEveryPool(context.TODO())
	assert.NoError(t, err)
	expected := `{"pools":[{"poolID":"123"},{}],"nonAvailablePools":["http://rms?pool=test"],"aliasDict":{"":0,"default":0,"reserved":1}}`
	assert.JSONEq(t, expected, mgr.String())
}

type assumeStub struct {
	Record AssumeRecord
	Kept   bool
}

func (a assumeStub) Assume(ctx JobContext, testbedName string) AssumeRecord {
	return a.Record
}

func (a assumeStub) ShouldKeepAssume(ctx context.Context, record AssumeRecord) bool {
	return a.Kept
}

func TestResourcePoolMgr_Assume(t *testing.T) {
	mgr := ResourcePoolMgr{poolMetaProvider: CmdlinePoolMetaProvider{rmsUrl: "http://rms", pools: []string{"default"}}}
	mgr.init()
	err := mgr.refreshPoolMeta()
	pool := &ResourcePool{cache: getSampleCacheResourcePoolMetric(), assume: assumeStub{Record: AssumeRecord{}}}
	mgr.pools[0] = pool
	assert.NoError(t, err)
	beds := toITestbeds(
		&commM.TestBed{RmsUrl: "http://rms", ResourcePool: "default", Name: "testbed3"},
	)
	err = mgr.Assume(GetSimpleJobCtx(1), beds)
	assert.NoError(t, err)
	assert.Contains(t, pool.cache.Assumed, "testbed3")
}

// test ResourcePool

var sampleRemoteMetric = ResourcePoolMetric{
	Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
	Testbeds: map[string]PhysicalResource{
		"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 1e9}},
		"testbed2": {CPU: commM.CPUResource{Core: 5}, Memory: commM.MemoryResource{Byte: 2e9}},
	},
}

type RmsStub struct {
	retObj        ResourcePoolMetric
	retErr        error
	externalVisit int
	url           string
}

func (r *RmsStub) PullResourcePoolMetric(rmPoolName string) (ResourcePoolMetric, error) {
	r.externalVisit++
	return r.retObj, r.retErr
}

func (r *RmsStub) Url() string {
	return r.url
}

func getSampleCacheResourcePoolMetric() ResourcePoolMetric {
	return ResourcePoolMetric{
		Capacity:             PhysicalResource{CPU: commM.CPUResource{Core: 100}, Memory: commM.MemoryResource{Byte: 100e9}},
		NonTestbedOccupation: PhysicalResource{CPU: commM.CPUResource{Core: 20}, Memory: commM.MemoryResource{Byte: 10e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 10e9}},
			"testbed2": {CPU: commM.CPUResource{Core: 20}, Memory: commM.MemoryResource{Byte: 7e9}},
		},
		Assumed: map[string]AssumeRecord{"testbed2": {CreateAt: time.Unix(0, 0).UTC(), OwnerJobID: 1}},
	}
}

func TestResourcePool_Copy(t *testing.T) {
	pool := &ResourcePool{cache: getSampleCacheResourcePoolMetric()}
	poolCopy := pool.Copy().(*ResourcePool)
	assert.Equal(t, pool, poolCopy)
	delete(poolCopy.cache.Testbeds, "testbed2")
	assert.Contains(t, pool.cache.Testbeds, "testbed2")

	poolCopy.cache.Capacity.CPU.Core = 1
	assert.Equal(t, float64(100), pool.cache.Capacity.CPU.Core)
}

func TestResourcePool_Assume(t *testing.T) {
	pool := &ResourcePool{cache: getSampleCacheResourcePoolMetric(),
		assume: assumeStub{Record: AssumeRecord{CreateAt: time.Unix(100, 5)}}}
	pool.init()
	oldFree := pool.cache.GetFree()
	res := PhysicalResource{CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 80e9}}
	err := pool.Assume(JobContext{job: &common.ArgoJob{PlanExec: &commM.TestPlanExecution{ID: 1}}}, "testbed3", res)
	assert.NoError(t, err)
	assert.Equal(t, time.Unix(100, 5), pool.cache.Assumed["testbed3"].CreateAt)
	assert.Equal(t, res, pool.cache.Testbeds["testbed3"])
	newFree := pool.cache.GetFree()
	assert.Equal(t, res, oldFree.Sub(newFree))
}

func TestResourcePool_IsResourceEnough(t *testing.T) {
	pool := &ResourcePool{cache: getSampleCacheResourcePoolMetric(), assume: assumeStub{}, name: "pool1"}
	pool.init()
	poolCopy := pool.Copy().(*ResourcePool)
	notEnough := pool.CheckResourceEnough(PhysicalResource{CPU: commM.CPUResource{Core: 100}, Memory: commM.MemoryResource{Byte: 80e9}})
	assert.Equal(t, ResCheckStatus{Status: ResCheckStatusInsufficient,
		Message: "insufficient resource, requested: {Cpu:100.00c, mem: 76294MiB}, pool 'pool1' free: {Cpu:50.00c, mem: 69618MiB}"}, notEnough)
	enough := pool.CheckResourceEnough(PhysicalResource{CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 40e9}})
	assert.Equal(t, ResCheckEnough, enough)
	exceeded := pool.CheckResourceEnough(PhysicalResource{CPU: commM.CPUResource{Core: 110}, Memory: commM.MemoryResource{Byte: 90e9}})
	assert.Equal(t, ResCheckStatus{Status: ResCheckStatusError,
		Message: "request {Cpu:110.00c, mem: 85831MiB} exceed pool 'pool1' capacity: {Cpu:100.00c, mem: 95367MiB}"}, exceeded)
	assert.Equal(t, pool.cache, poolCopy.cache)
	pool.tempDown = true
	down := pool.CheckResourceEnough(PhysicalResource{})
	assert.Equal(t, ResCheckStatus{Status: ResCheckStatusTempDown, Message: "pool 'pool1' is temporary down"}, down)
}

func TestResourcePool_Refresh_Fail(t *testing.T) {
	rms := &RmsStub{retObj: sampleRemoteMetric, retErr: fmt.Errorf("test fail")}
	pool := ResourcePool{rms: rms, cache: ResourcePoolMetric{}}
	err := pool.Refresh(context.TODO())
	assert.NoError(t, err)
	assert.True(t, pool.tempDown)
	status := pool.CheckResourceEnough(PhysicalResource{})
	assert.Equal(t, ResCheckStatusTempDown, status.Status)
}

func TestResourcePool_Refresh_Success(t *testing.T) {
	rms := &RmsStub{retObj: sampleRemoteMetric}
	pool := ResourcePool{rms: rms, cache: ResourcePoolMetric{}}
	pool.init()
	err := pool.Refresh(context.TODO())
	assert.NoError(t, err)
	expected := sampleRemoteMetric
	expected.Assumed = map[string]AssumeRecord{}
	assert.Equal(t, expected, pool.cache)
	assert.False(t, pool.tempDown)
}

func TestResourcePool_Merge_Empty_Remote(t *testing.T) {
	pool := ResourcePool{cache: ResourcePoolMetric{}, assume: assumeStub{}}
	pool.init()
	emptyRemote := ResourcePoolMetric{}
	pool.Merge(context.TODO(), emptyRemote)
	assert.NotNil(t, pool.cache.Assumed)
	assert.NotNil(t, pool.cache.Testbeds)
}

func TestResourcePool_Merge_Assume_Larger(t *testing.T) {
	assumed := ResourcePoolMetric{
		Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed2": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 2e9}},
		},
		Assumed: map[string]AssumeRecord{"testbed2": {CreateAt: time.Unix(0, 0)}},
	}
	pool := ResourcePool{cache: assumed, assume: assumeStub{Kept: true}}

	expected := ResourcePoolMetric{
		Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 1e9}},
			"testbed2": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 2e9}},
		},
		Assumed: map[string]AssumeRecord{"testbed2": {CreateAt: time.Unix(0, 0)}},
	}
	pool.Merge(context.TODO(), sampleRemoteMetric)
	assert.Equal(t, expected, pool.cache)
}

func TestResourcePool_Merge_Remote_Larger(t *testing.T) {
	assumed := ResourcePoolMetric{
		Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed2": {CPU: commM.CPUResource{Core: 1}, Memory: commM.MemoryResource{Byte: 1e9}},
		},
		Assumed: map[string]AssumeRecord{"testbed2": {CreateAt: time.Unix(0, 0)}},
	}
	pool := ResourcePool{cache: assumed, assume: assumeStub{Kept: true}}

	expected := ResourcePoolMetric{
		Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 1e9}},
			"testbed2": {CPU: commM.CPUResource{Core: 5}, Memory: commM.MemoryResource{Byte: 2e9}},
		},
		Assumed: map[string]AssumeRecord{"testbed2": {CreateAt: time.Unix(0, 0)}},
	}
	pool.Merge(context.TODO(), sampleRemoteMetric)
	assert.Equal(t, expected, pool.cache)
}

func TestResourcePool_Merge_Assume_Expired(t *testing.T) {
	assumed := ResourcePoolMetric{
		Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed2": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 1e9}},
		},
		Assumed: map[string]AssumeRecord{"testbed2": {CreateAt: time.Unix(0, 0)}},
	}
	pool := ResourcePool{cache: assumed, assume: assumeStub{Kept: false}}

	expected := ResourcePoolMetric{
		Capacity: PhysicalResource{CPU: commM.CPUResource{Core: 40}, Memory: commM.MemoryResource{Byte: 4e9}},
		Testbeds: map[string]PhysicalResource{
			"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 1e9}},
			"testbed2": {CPU: commM.CPUResource{Core: 5}, Memory: commM.MemoryResource{Byte: 2e9}},
		},
		Assumed: map[string]AssumeRecord{},
	}
	pool.Merge(context.TODO(), sampleRemoteMetric)
	assert.Equal(t, expected, pool.cache)
}

func TestResourcePool_String(t *testing.T) {
	pool := ResourcePool{cache: getSampleCacheResourcePoolMetric(), rms: &RmsStub{url: "http://rms"}, name: "default"}
	poolStr := fmt.Sprintf(`{"id":0,"name":"default","cache":%s}`, sampleCacheResourcePoolMetricString)
	assert.JSONEq(t, poolStr, pool.String())
}

// test ResourcePoolMetric

func TestResourcePoolMetric_GetFree(t *testing.T) {
	f := getSampleCacheResourcePoolMetric().GetFree()
	assert.Equal(t, PhysicalResource{CPU: commM.CPUResource{Core: 50}, Memory: commM.MemoryResource{Byte: 73e9}}, f)
}

// test rms

type httpClientStub struct {
	requests  []*http.Request
	responses []http.Response
	errs      []error
	index     int
}

func (h *httpClientStub) Do(req *http.Request) (*http.Response, error) {
	h.requests = append(h.requests, req)
	h.index++
	return &h.responses[h.index-1], h.errs[h.index-1]
}

func (h *httpClientStub) SetJsonResponse(obj interface{}, code int) error {
	buffer := &bytes.Buffer{}
	err := json.NewEncoder(buffer).Encode(obj)
	if err != nil {
		return err
	}
	h.responses = append(h.responses, http.Response{Body: io.NopCloser(buffer), StatusCode: code})
	h.errs = append(h.errs, nil)
	return nil
}

func TestRms_pullResourcePoolStatus(t *testing.T) {
	r := rms{url: "http://rms"}
	stub := &httpClientStub{}
	r.httpClient = stub
	capacity := commM.PhysicalResource{CPU: commM.CPUResource{Core: 100}, Memory: commM.MemoryResource{Byte: 100e9}}
	guest := commM.PhysicalResource{CPU: commM.CPUResource{Core: 20}, Memory: commM.MemoryResource{Byte: 10e9}}
	rt := ResourcePoolMetric{}
	err := stub.SetJsonResponse(
		rmsRespM.GetResourcePoolStatusResponse{
			TotalResource:        capacity,
			NonTestbedOccupation: guest,
		},
		http.StatusOK)
	assert.NoError(t, err)
	rt.Capacity, rt.NonTestbedOccupation, err = r.pullResourcePoolStatus("testPool")
	assert.NoError(t, err)
	assert.Equal(t, PhysicalResource(capacity), rt.Capacity)
	assert.Equal(t, PhysicalResource(guest), rt.NonTestbedOccupation)
	assert.Equal(t, "http://rms/api/v1/resource-pool/status", stub.requests[0].URL.String())
	bodyBytes, err := io.ReadAll(stub.requests[0].Body)
	assert.NoError(t, err)
	assert.JSONEq(t, `{"resourcePool":"testPool"}`, string(bodyBytes))
}

func TestRms_pullResourcePoolStatus_fail(t *testing.T) {
	r := rms{url: "http://rms"}
	stub := &httpClientStub{}
	r.httpClient = stub
	rt := ResourcePoolMetric{}
	err := stub.SetJsonResponse(
		nil,
		http.StatusBadRequest)
	assert.NoError(t, err)
	rt.Capacity, rt.NonTestbedOccupation, err = r.pullResourcePoolStatus("testPool")
	assert.Error(t, err)
	assert.Equal(t, "http://rms/api/v1/resource-pool/status", stub.requests[0].URL.String())
}

func TestRms_pullTestbedsStatus(t *testing.T) {
	r := rms{url: "http://rms"}
	stub := &httpClientStub{}
	r.httpClient = stub

	testbeds := map[string]*commM.PhysicalResource{
		"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 10e9}},
		"testbed2": {CPU: commM.CPUResource{Core: 20}, Memory: commM.MemoryResource{Byte: 7e9}},
	}
	rt := ResourcePoolMetric{}
	err := stub.SetJsonResponse(
		rmsRespM.GetTestbedsResourceResponse{
			TestbedsOccupation: testbeds,
		},
		http.StatusOK)
	assert.NoError(t, err)

	rt.Testbeds, err = r.pullTestbedsStatus("testPool")
	expected := map[string]PhysicalResource{
		"testbed1": {CPU: commM.CPUResource{Core: 10}, Memory: commM.MemoryResource{Byte: 10e9}},
		"testbed2": {CPU: commM.CPUResource{Core: 20}, Memory: commM.MemoryResource{Byte: 7e9}},
	}
	assert.NoError(t, err)
	assert.Equal(t, expected, rt.Testbeds)
	assert.Equal(t, "http://rms/api/v1/resource-pool/testbeds-resource", stub.requests[0].URL.String())
	bodyBytes, err := io.ReadAll(stub.requests[0].Body)
	assert.NoError(t, err)
	assert.JSONEq(t, `{"resourcePool":"testPool"}`, string(bodyBytes))
}

func TestRms_request(t *testing.T) {
	r := rms{url: "http://rms"}
	stub := &httpClientStub{}
	err := stub.SetJsonResponse(
		PhysicalResource{CPU: commM.CPUResource{Core: 3}},
		http.StatusOK)
	assert.NoError(t, err)
	r.httpClient = stub
	req := PhysicalResource{}
	response := &PhysicalResource{}
	err = r.request("api/v1/test/path", req, response)
	assert.NoError(t, err)
	request := stub.requests[0]
	assert.Equal(t, "http://rms/api/v1/test/path", request.URL.String())
	assert.Equal(t, "application/json", request.Header.Get("Content-Type"))
	assert.Equal(t, float64(3), response.CPU.Core)
}

func TestRms_Url(t *testing.T) {
	var r RmsInterface = rms{url: "http://rms"}
	assert.Equal(t, "http://rms", r.Url())
}

var sampleCacheResourcePoolMetricString = `{
    "capacity": {
        "cpu": {
            "core": 100
        },
        "memory": {
            "byte": 100000000000
        }
    },
    "nonTestbedOccupation": {
        "cpu": {
            "core": 20
        },
        "memory": {
            "byte": 10000000000
        }
    },
    "testbeds": {
        "testbed1": {
            "cpu": {
                "core": 10
            },
            "memory": {
                "byte": 10000000000
            }
        },
        "testbed2": {
            "cpu": {
                "core": 20
            },
            "memory": {
                "byte": 7000000000
            }
        }
    },
    "assumed": {
        "testbed2": {
            "createAt": "1970-01-01T00:00:00Z",
            "ownerJobID":1
        }
    },
    "free": {
        "cpu": {
            "core": 50
        },
        "memory": {
            "byte": 73000000000
        }
    }
}`

func TestResourcePoolMetric_String(t *testing.T) {
	v := getSampleCacheResourcePoolMetric().String()
	assert.JSONEq(t, sampleCacheResourcePoolMetricString, v)
}

type storeStub struct {
	job *common.ArgoJob
}

func (s storeStub) PutJob(ctx context.Context, job *common.ArgoJob, action common.OnDupAction) error {
	panic("implement me")
}

func (s storeStub) GetJob(ctx context.Context, id commM.ID) (*common.ArgoJob, error) {
	return s.job, nil
}

func (s storeStub) EditJob(ctx context.Context, id commM.ID, p common.ArgoJobProcessor) error {
	panic("implement me")
}

func (s storeStub) ListJobs(ctx context.Context, conds common.ListJobConditions) ([]*common.ArgoJob, error) {
	panic("implement me")
}

func TestDefaultAssumeNoExpire(t *testing.T) {
	ass := defaultAssume{}
	act := ass.ShouldKeepAssume(context.TODO(), AssumeRecord{CreateAt: time.Now().Add(-(MinAssumeDuration - time.Minute))})
	assert.True(t, act)
}

func TestDefaultAssumeRunningJobNoExpire(t *testing.T) {
	ass := defaultAssume{storeStub{job: &common.ArgoJob{Status: common.ArgoTestJobRunning}}}
	act := ass.ShouldKeepAssume(context.TODO(), AssumeRecord{CreateAt: time.Now().Add(-(MinAssumeDuration + time.Minute))})
	assert.True(t, act)
}

func TestDefaultAssumeDoneJobExpire(t *testing.T) {
	ass := defaultAssume{storeStub{job: &common.ArgoJob{Status: common.ArgoTestJobDone}}}
	act := ass.ShouldKeepAssume(context.TODO(), AssumeRecord{CreateAt: time.Now().Add(-(MinAssumeDuration + time.Minute))})
	assert.False(t, act)
}
