MM-21722 - Repository synchronization tool (#86)
This commit is contained in:
parent
4e7a2d3734
commit
0688e8df4c
18 changed files with 1589 additions and 0 deletions
253
build/sync/plan/plan_test.go
Normal file
253
build/sync/plan/plan_test.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package plan_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mattermost/mattermost-plugin-starter-template/build/sync/plan"
|
||||
)
|
||||
|
||||
func TestUnmarshalPlan(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
rawJSON := []byte(`
|
||||
{
|
||||
"checks": [
|
||||
{"type": "repo_is_clean", "params": {"repo": "template"}}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"paths": ["abc"],
|
||||
"actions": [{
|
||||
"type": "overwrite_file",
|
||||
"params": {"create": true},
|
||||
"conditions": [{
|
||||
"type": "exists",
|
||||
"params": {"repo": "plugin"}
|
||||
}]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
var p plan.Plan
|
||||
err := json.Unmarshal(rawJSON, &p)
|
||||
assert.Nil(err)
|
||||
expectedCheck := plan.RepoIsCleanChecker{}
|
||||
expectedCheck.Params.Repo = "template"
|
||||
|
||||
expectedAction := plan.OverwriteFileAction{}
|
||||
expectedAction.Params.Create = true
|
||||
expectedActionCheck := plan.PathExistsChecker{}
|
||||
expectedActionCheck.Params.Repo = "plugin"
|
||||
expectedAction.Conditions = []plan.Check{&expectedActionCheck}
|
||||
expected := plan.Plan{
|
||||
Checks: []plan.Check{&expectedCheck},
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"abc"},
|
||||
Actions: []plan.Action{
|
||||
&expectedAction,
|
||||
},
|
||||
}},
|
||||
}
|
||||
assert.Equal(expected, p)
|
||||
}
|
||||
|
||||
type mockCheck struct {
|
||||
returnErr error
|
||||
calledWith string // Path parameter the check was called with.
|
||||
}
|
||||
|
||||
// Check implements the plan.Check interface.
|
||||
func (m *mockCheck) Check(path string, c plan.Setup) error {
|
||||
m.calledWith = path
|
||||
return m.returnErr
|
||||
}
|
||||
|
||||
type mockAction struct {
|
||||
runErr error // Error to be returned by Run.
|
||||
checkErr error // Error to be returned by Check.
|
||||
calledWith string
|
||||
}
|
||||
|
||||
// Check implements plan.Action interface.
|
||||
func (m *mockAction) Check(path string, c plan.Setup) error {
|
||||
return m.checkErr
|
||||
}
|
||||
|
||||
// Run implements plan.Action interface.
|
||||
func (m *mockAction) Run(path string, c plan.Setup) error {
|
||||
m.calledWith = path
|
||||
return m.runErr
|
||||
}
|
||||
|
||||
// TestRunPlanSuccessfully tests a successful execution of a sync plan.
|
||||
func TestRunPlanSuccessfully(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
setup := plan.Setup{} // mocked actions and checks won't be accessing the setup.
|
||||
|
||||
preCheck := &mockCheck{}
|
||||
action1 := &mockAction{}
|
||||
action2 := &mockAction{}
|
||||
|
||||
p := &plan.Plan{
|
||||
Checks: []plan.Check{preCheck},
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"somepath"},
|
||||
Actions: []plan.Action{
|
||||
action1,
|
||||
action2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := p.Execute(setup)
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal("", preCheck.calledWith)
|
||||
assert.Equal("somepath", action1.calledWith)
|
||||
assert.Equal("", action2.calledWith) // second action was not called.
|
||||
}
|
||||
|
||||
// TestRunPlanPreCheckFail checks the scenario where a sync plan precheck
|
||||
// fails, aborting the whole operation.
|
||||
func TestRunPlanPreCheckFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
setup := plan.Setup{} // mocked actions and checks won't be accessing the setup.
|
||||
|
||||
preCheck := &mockCheck{returnErr: plan.CheckFailf("check failed")}
|
||||
action1 := &mockAction{}
|
||||
action2 := &mockAction{}
|
||||
|
||||
p := &plan.Plan{
|
||||
Checks: []plan.Check{preCheck},
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"somepath"},
|
||||
Actions: []plan.Action{
|
||||
action1,
|
||||
action2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := p.Execute(setup)
|
||||
assert.EqualError(err, "failed check: check failed")
|
||||
|
||||
assert.Equal("", preCheck.calledWith)
|
||||
// None of the actions were executed.
|
||||
assert.Equal("", action1.calledWith)
|
||||
assert.Equal("", action2.calledWith)
|
||||
}
|
||||
|
||||
// TestRunPlanActionCheckFails tests the situation where an action's
|
||||
// check returns a recoverable error, forcing the plan to execute the fallback action.
|
||||
func TestRunPlanActionCheckFails(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
setup := plan.Setup{} // mocked actions and checks won't be accessing the setup.
|
||||
|
||||
action1 := &mockAction{checkErr: plan.CheckFailf("action check failed")}
|
||||
action2 := &mockAction{}
|
||||
|
||||
p := &plan.Plan{
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"somepath"},
|
||||
Actions: []plan.Action{
|
||||
action1,
|
||||
action2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := p.Execute(setup)
|
||||
assert.Nil(err)
|
||||
|
||||
assert.Equal("", action1.calledWith) // First action was not run.
|
||||
assert.Equal("somepath", action2.calledWith) // Second action was run.
|
||||
}
|
||||
|
||||
// TestRunPlanNoFallbacks tests the case where an action's check fails,
|
||||
// but there are not more fallback actions for that path.
|
||||
func TestRunPlanNoFallbacks(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
setup := plan.Setup{} // mocked actions and checks won't be accessing the setup.
|
||||
|
||||
action1 := &mockAction{checkErr: plan.CheckFailf("fail")}
|
||||
action2 := &mockAction{checkErr: plan.CheckFailf("fail")}
|
||||
|
||||
p := &plan.Plan{
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"somepath"},
|
||||
Actions: []plan.Action{
|
||||
action1,
|
||||
action2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := p.Execute(setup)
|
||||
assert.Nil(err)
|
||||
|
||||
// both actions were not executed.
|
||||
assert.Equal("", action1.calledWith)
|
||||
assert.Equal("", action2.calledWith)
|
||||
}
|
||||
|
||||
// TestRunPlanCheckError tests the scenario where a plan check fails with
|
||||
// an unexpected error. Plan execution is aborted.
|
||||
func TestRunPlanCheckError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
setup := plan.Setup{} // mocked actions and checks won't be accessing the setup.
|
||||
|
||||
preCheck := &mockCheck{returnErr: fmt.Errorf("fail")}
|
||||
action1 := &mockAction{}
|
||||
action2 := &mockAction{}
|
||||
|
||||
p := &plan.Plan{
|
||||
Checks: []plan.Check{preCheck},
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"somepath"},
|
||||
Actions: []plan.Action{
|
||||
action1,
|
||||
action2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := p.Execute(setup)
|
||||
assert.EqualError(err, "failed check: fail")
|
||||
|
||||
assert.Equal("", preCheck.calledWith)
|
||||
// Actions were not run.
|
||||
assert.Equal("", action1.calledWith)
|
||||
assert.Equal("", action2.calledWith)
|
||||
}
|
||||
|
||||
// TestRunPlanActionError tests the scenario where an action fails,
|
||||
// aborting the whole sync process.
|
||||
func TestRunPlanActionError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
setup := plan.Setup{} // mocked actions and checks won't be accessing the setup.
|
||||
|
||||
preCheck := &mockCheck{}
|
||||
action1 := &mockAction{runErr: fmt.Errorf("fail")}
|
||||
action2 := &mockAction{}
|
||||
|
||||
p := &plan.Plan{
|
||||
Checks: []plan.Check{preCheck},
|
||||
Actions: []plan.ActionSet{{
|
||||
Paths: []string{"somepath"},
|
||||
Actions: []plan.Action{
|
||||
action1,
|
||||
action2,
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := p.Execute(setup)
|
||||
assert.EqualError(err, "action failed: fail")
|
||||
|
||||
assert.Equal("", preCheck.calledWith)
|
||||
assert.Equal("somepath", action1.calledWith)
|
||||
assert.Equal("", action2.calledWith) // second action was not called.
|
||||
}
|
Reference in a new issue