package api

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http/httptest"
	"testing"

	"gopkg.in/yaml.v2"

	"github.com/gotify/server/v2/mode"
	"github.com/gotify/server/v2/model"
	"github.com/gotify/server/v2/plugin"
	"github.com/gotify/server/v2/plugin/compat"
	"github.com/gotify/server/v2/plugin/testing/mock"
	"github.com/gotify/server/v2/test"
	"github.com/gotify/server/v2/test/testdb"

	"github.com/gin-gonic/gin"

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

func TestPluginSuite(t *testing.T) {
	suite.Run(t, new(PluginSuite))
}

type PluginSuite struct {
	suite.Suite
	db       *testdb.Database
	a        *PluginAPI
	ctx      *gin.Context
	recorder *httptest.ResponseRecorder
	manager  *plugin.Manager
	notified bool
}

func (s *PluginSuite) BeforeTest(suiteName, testName string) {
	mode.Set(mode.TestDev)
	s.db = testdb.NewDB(s.T())
	s.resetRecorder()
	manager, err := plugin.NewManager(s.db, "", nil, s)
	assert.Nil(s.T(), err)
	s.manager = manager
	withURL(s.ctx, "http", "example.com")
	s.a = &PluginAPI{DB: s.db, Manager: manager, Notifier: s}

	mockPluginCompat := new(mock.Plugin)
	assert.Nil(s.T(), s.manager.LoadPlugin(mockPluginCompat))

	s.db.User(1)
	assert.Nil(s.T(), s.manager.InitializeForUserID(1))
	s.db.User(2)
	assert.Nil(s.T(), s.manager.InitializeForUserID(2))

	s.db.CreatePluginConf(&model.PluginConf{
		UserID:     1,
		ModulePath: "github.com/gotify/server/v2/plugin/example/removed",
		Token:      "P1234",
		Enabled:    false,
	})
}

func (s *PluginSuite) getDanglingConf(uid uint) *model.PluginConf {
	conf, err := s.db.GetPluginConfByUserAndPath(uid, "github.com/gotify/server/v2/plugin/example/removed")
	assert.NoError(s.T(), err)
	return conf
}

func (s *PluginSuite) resetRecorder() {
	s.recorder = httptest.NewRecorder()
	s.ctx, _ = gin.CreateTestContext(s.recorder)
}

func (s *PluginSuite) AfterTest(suiteName, testName string) {
	s.db.Close()
}

func (s *PluginSuite) Notify(userID uint, msg *model.MessageExternal) {
	s.notified = true
}

func (s *PluginSuite) Test_GetPlugins() {
	test.WithUser(s.ctx, 1)

	s.ctx.Request = httptest.NewRequest("GET", "/plugin", nil)
	s.a.GetPlugins(s.ctx)

	assert.Equal(s.T(), 200, s.recorder.Code)

	pluginConfs := make([]model.PluginConfExternal, 0)
	assert.Nil(s.T(), json.Unmarshal(s.recorder.Body.Bytes(), &pluginConfs))

	assert.Equal(s.T(), mock.Name, pluginConfs[0].Name)
	assert.Equal(s.T(), mock.ModulePath, pluginConfs[0].ModulePath)

	assert.False(s.T(), pluginConfs[0].Enabled, "Plugins should be disabled by default")
}

func (s *PluginSuite) Test_EnableDisablePlugin() {

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/1/enable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
		s.a.EnablePlugin(s.ctx)

		assert.Equal(s.T(), 200, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.True(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/1/enable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
		s.a.EnablePlugin(s.ctx)

		assert.Equal(s.T(), 400, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.True(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/1/disable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
		s.a.DisablePlugin(s.ctx)

		assert.Equal(s.T(), 200, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.False(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/1/disable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
		s.a.DisablePlugin(s.ctx)

		assert.Equal(s.T(), 400, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.False(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}

}

func (s *PluginSuite) Test_EnableDisablePlugin_EnableReturnsError_expect500() {
	s.db.User(16)
	assert.Nil(s.T(), s.manager.InitializeForUserID(16))
	mock.ReturnErrorOnEnableForUser(16, errors.New("test error"))
	conf, err := s.db.GetPluginConfByUserAndPath(16, mock.ModulePath)
	assert.NoError(s.T(), err)

	{
		test.WithUser(s.ctx, 16)
		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/enable", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.EnablePlugin(s.ctx)

		assert.Equal(s.T(), 500, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.False(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}
}

func (s *PluginSuite) Test_EnableDisablePlugin_DisableReturnsError_expect500() {
	s.db.User(17)
	assert.Nil(s.T(), s.manager.InitializeForUserID(17))
	mock.ReturnErrorOnDisableForUser(17, errors.New("test error"))
	conf, err := s.db.GetPluginConfByUserAndPath(17, mock.ModulePath)
	assert.NoError(s.T(), err)
	s.manager.SetPluginEnabled(conf.ID, true)

	{
		test.WithUser(s.ctx, 17)
		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/disable", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.DisablePlugin(s.ctx)

		assert.Equal(s.T(), 500, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.False(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}
}

func (s *PluginSuite) Test_EnableDisablePlugin_incorrectUser_expectNotFound() {
	{
		test.WithUser(s.ctx, 2)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/1/enable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
		s.a.EnablePlugin(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.False(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}

	{
		test.WithUser(s.ctx, 2)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/1/disable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "1"}}
		s.a.DisablePlugin(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)

		if pluginConf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath); assert.NoError(s.T(), err) {
			assert.False(s.T(), pluginConf.Enabled)
		}
		s.resetRecorder()
	}

}

func (s *PluginSuite) Test_EnableDisablePlugin_nonExistPlugin_expectNotFound() {
	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/99/enable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "99"}}
		s.a.EnablePlugin(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
		s.resetRecorder()
	}

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/99/disable", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "99"}}
		s.a.DisablePlugin(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
		s.resetRecorder()
	}

}

func (s *PluginSuite) Test_EnableDisablePlugin_danglingConf_expectNotFound() {
	conf := s.getDanglingConf(1)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/enable", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.EnablePlugin(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
		s.resetRecorder()
	}

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/disable", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.DisablePlugin(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
		s.resetRecorder()
	}
}

func (s *PluginSuite) Test_GetDisplay() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	mockInst.DisplayString = "test string"

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/display", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetDisplay(s.ctx)

		assert.Equal(s.T(), 200, s.recorder.Code)
		test.JSONEquals(s.T(), mockInst.DisplayString, s.recorder.Body.String())
	}
}

func (s *PluginSuite) Test_GetDisplay_NotImplemented_expectEmptyString() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	mockInst.SetCapability(compat.Displayer, false)
	defer mockInst.SetCapability(compat.Displayer, true)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/display", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetDisplay(s.ctx)

		assert.Equal(s.T(), 200, s.recorder.Code)
		test.JSONEquals(s.T(), "", s.recorder.Body.String())
	}
}

func (s *PluginSuite) Test_GetDisplay_incorrectUser_expectNotFound() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	mockInst.DisplayString = "test string"

	{
		test.WithUser(s.ctx, 2)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/display", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetDisplay(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_GetDisplay_danglingConf_expectNotFound() {
	conf := s.getDanglingConf(1)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/display", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetDisplay(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_GetDisplay_nonExistPlugin_expectNotFound() {
	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", "/plugin/99/display", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "99"}}
		s.a.GetDisplay(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_GetConfig() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	assert.Equal(s.T(), mockInst.DefaultConfig(), mockInst.Config, "Initial config should be default config")
	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/config", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetConfig(s.ctx)

		assert.Equal(s.T(), 200, s.recorder.Code)
		returnedConfig := new(mock.PluginConfig)
		assert.Nil(s.T(), yaml.Unmarshal(s.recorder.Body.Bytes(), returnedConfig))
		assert.Equal(s.T(), mockInst.Config, returnedConfig)
	}
}

func (s *PluginSuite) Test_GetConfg_notImplemeted_expect400() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	mockInst.SetCapability(compat.Configurer, false)
	defer mockInst.SetCapability(compat.Configurer, true)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/config", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetConfig(s.ctx)

		assert.Equal(s.T(), 400, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_GetConfig_incorrectUser_expectNotFound() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)

	{
		test.WithUser(s.ctx, 2)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/config", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetConfig(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_GetConfig_danglingConf_expectNotFound() {
	conf := s.getDanglingConf(1)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", fmt.Sprintf("/plugin/%d/config", conf.ID), nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.GetConfig(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_GetConfig_nonExistPlugin_expectNotFound() {
	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("GET", "/plugin/99/config", nil)
		s.ctx.Params = gin.Params{{Key: "id", Value: "99"}}
		s.a.GetConfig(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_UpdateConfig() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	newConfig := &mock.PluginConfig{
		TestKey: "test__new__config",
	}
	newConfigYAML, err := yaml.Marshal(newConfig)
	assert.Nil(s.T(), err)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 200, s.recorder.Code)
		assert.Equal(s.T(), newConfig, mockInst.Config, "config should be received by plugin")

		var pluginFromDBBytes []byte
		if pluginConf, err := s.db.GetPluginConfByID(conf.ID); assert.NoError(s.T(), err) {
			pluginFromDBBytes = pluginConf.Config
		}
		pluginFromDB := new(mock.PluginConfig)
		err := yaml.Unmarshal(pluginFromDBBytes, pluginFromDB)
		assert.Nil(s.T(), err)
		assert.Equal(s.T(), newConfig, pluginFromDB, "config should be updated in database")
	}
}

func (s *PluginSuite) Test_UpdateConfig_invalidConfig_expect400() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)
	origConfig := mockInst.Config

	newConfig := &mock.PluginConfig{
		TestKey:    "test__new__config__invalid",
		IsNotValid: true,
	}
	newConfigYAML, err := yaml.Marshal(newConfig)
	assert.Nil(s.T(), err)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 400, s.recorder.Code)
		assert.Equal(s.T(), origConfig, mockInst.Config, "config should not be received by plugin")

		var pluginFromDBBytes []byte
		if pluginConf, err := s.db.GetPluginConfByID(conf.ID); assert.NoError(s.T(), err) {
			pluginFromDBBytes = pluginConf.Config
		}
		pluginFromDB := new(mock.PluginConfig)
		err := yaml.Unmarshal(pluginFromDBBytes, pluginFromDB)
		assert.Nil(s.T(), err)
		assert.Equal(s.T(), origConfig, pluginFromDB, "config should not be updated in database")
	}
}

func (s *PluginSuite) Test_UpdateConfig_malformedYAML_expect400() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)
	origConfig := mockInst.Config

	newConfigYAML := []byte(`--- "rg e""`)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 400, s.recorder.Code)
		assert.Equal(s.T(), origConfig, mockInst.Config, "config should not be received by plugin")

		var pluginFromDBBytes []byte
		if pluginConf, err := s.db.GetPluginConfByID(conf.ID); assert.NoError(s.T(), err) {
			pluginFromDBBytes = pluginConf.Config
		}
		pluginFromDB := new(mock.PluginConfig)
		err := yaml.Unmarshal(pluginFromDBBytes, pluginFromDB)
		assert.Nil(s.T(), err)
		assert.Equal(s.T(), origConfig, pluginFromDB, "config should not be updated in database")
	}
}

func (s *PluginSuite) Test_UpdateConfig_ioError_expect500() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)
	origConfig := mockInst.Config

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), test.UnreadableReader())
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 500, s.recorder.Code)
		assert.Equal(s.T(), origConfig, mockInst.Config, "config should not be received by plugin")

		var pluginFromDBBytes []byte
		if pluginConf, err := s.db.GetPluginConfByID(conf.ID); assert.NoError(s.T(), err) {
			pluginFromDBBytes = pluginConf.Config
		}
		pluginFromDB := new(mock.PluginConfig)
		err := yaml.Unmarshal(pluginFromDBBytes, pluginFromDB)
		assert.Nil(s.T(), err)
		assert.Equal(s.T(), origConfig, pluginFromDB, "config should not be updated in database")
	}
}

func (s *PluginSuite) Test_UpdateConfig_notImplemented_expect400() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)

	newConfig := &mock.PluginConfig{
		TestKey: "test__new__config",
	}
	newConfigYAML, err := yaml.Marshal(newConfig)
	assert.Nil(s.T(), err)

	mockInst.SetCapability(compat.Configurer, false)
	defer mockInst.SetCapability(compat.Configurer, true)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 400, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_UpdateConfig_incorrectUser_expectNotFound() {
	conf, err := s.db.GetPluginConfByUserAndPath(1, mock.ModulePath)
	assert.NoError(s.T(), err)
	inst, err := s.manager.Instance(conf.ID)
	assert.Nil(s.T(), err)
	mockInst := inst.(*mock.PluginInstance)
	origConfig := mockInst.Config

	newConfig := &mock.PluginConfig{
		TestKey: "test__new__config",
	}
	newConfigYAML, err := yaml.Marshal(newConfig)
	assert.Nil(s.T(), err)

	{
		test.WithUser(s.ctx, 2)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
		assert.Equal(s.T(), origConfig, mockInst.Config, "config should not be received by plugin")

		var pluginFromDBBytes []byte
		if pluginConf, err := s.db.GetPluginConfByID(conf.ID); assert.NoError(s.T(), err) {
			pluginFromDBBytes = pluginConf.Config
		}
		pluginFromDB := new(mock.PluginConfig)
		err := yaml.Unmarshal(pluginFromDBBytes, pluginFromDB)
		assert.Nil(s.T(), err)
		assert.Equal(s.T(), origConfig, pluginFromDB, "config should not be updated in database")
	}
}

func (s *PluginSuite) Test_UpdateConfig_danglingConf_expectNotFound() {
	conf := s.getDanglingConf(1)

	newConfig := &mock.PluginConfig{
		TestKey: "test__new__config",
	}
	newConfigYAML, err := yaml.Marshal(newConfig)
	assert.Nil(s.T(), err)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", fmt.Sprintf("/plugin/%d/config", conf.ID), bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: fmt.Sprint(conf.ID)}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}

func (s *PluginSuite) Test_UpdateConfig_nonExistPlugin_expectNotFound() {
	newConfig := &mock.PluginConfig{
		TestKey: "test__new__config",
	}
	newConfigYAML, err := yaml.Marshal(newConfig)
	assert.Nil(s.T(), err)

	{
		test.WithUser(s.ctx, 1)

		s.ctx.Request = httptest.NewRequest("POST", "/plugin/99/config", bytes.NewReader(newConfigYAML))
		s.ctx.Header("Content-Type", "application/x-yaml")
		s.ctx.Params = gin.Params{{Key: "id", Value: "99"}}
		s.a.UpdateConfig(s.ctx)

		assert.Equal(s.T(), 404, s.recorder.Code)
	}
}
