package generic
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/build/webhook"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/unversioned"
)
var mockBuildStrategy = api.BuildStrategy{
SourceStrategy: &api.SourceBuildStrategy{
From: kapi.ObjectReference{
Name: "repository/image",
},
},
}
func GivenRequest(method string) *http.Request {
req, _ := http.NewRequest(method, "http://someurl.com", nil)
return req
}
func GivenRequestWithPayload(t *testing.T, filename string) *http.Request {
return GivenRequestWithPayloadAndContentType(t, filename, "application/json")
}
func GivenRequestWithPayloadAndContentType(t *testing.T, filename, contentType string) *http.Request {
data, err := ioutil.ReadFile("testdata/" + filename)
if err != nil {
t.Errorf("Error reading setup data: %v", err)
return nil
}
req, _ := http.NewRequest("POST", "http://someurl.com", bytes.NewReader(data))
req.Header.Add("Content-Type", contentType)
return req
}
func GivenRequestWithRefsPayload(t *testing.T) *http.Request {
data, err := ioutil.ReadFile("testdata/post-receive-git.json")
if err != nil {
t.Errorf("Error reading setup data: %v", err)
return nil
}
req, _ := http.NewRequest("POST", "http://someurl.com", bytes.NewReader(data))
req.Header.Add("Content-Type", "application/json")
return req
}
func matchWarning(t *testing.T, err error, message string) {
status, ok := err.(*errors.StatusError)
if !ok {
t.Errorf("Expected %v to be a StatusError object", err)
return
}
if status.ErrStatus.Status != unversioned.StatusSuccess {
t.Errorf("Unexpected response status %v, expected %v", status.ErrStatus.Status, unversioned.StatusSuccess)
}
if status.ErrStatus.Code != http.StatusOK {
t.Errorf("Unexpected response code %v, expected %v", status.ErrStatus.Code, http.StatusOK)
}
if status.ErrStatus.Message != message {
t.Errorf("Unexpected response message %v, expected %v", status.ErrStatus.Message, message)
}
}
func TestVerifyRequestForMethod(t *testing.T) {
req := GivenRequest("GET")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err == nil || !strings.Contains(err.Error(), "unsupported HTTP method") {
t.Errorf("Expected unsupported HTTP method, got %v!", err)
}
if proceed {
t.Error("Expected 'proceed' return value to be 'false'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
func TestWrongSecretMultipleGenericWebHooks(t *testing.T) {
req := GivenRequest("GET")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret101",
},
},
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret102",
},
},
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "wrongsecret", "", req)
if err != webhook.ErrSecretMismatch {
t.Errorf("Expected %s, got %s", webhook.ErrSecretMismatch, err)
}
if proceed {
t.Error("Expected 'proceed' to return 'false'")
}
if revision != nil {
t.Errorf("Expected the 'revision' to be nil, go %v instead", revision)
}
}
func TestMatchSecretMultipleGenericWebHooks(t *testing.T) {
req := GivenRequestWithPayload(t, "push-generic.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret101",
},
},
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret102",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret102", "", req)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
if !proceed {
t.Error("Expected 'proceed' to return 'true', got 'false' instead")
}
if revision == nil {
t.Errorf("Expected the 'revision' to not be nil")
}
}
func TestEnvVarsMultipleGenericWebHooks(t *testing.T) {
req := GivenRequestWithPayload(t, "push-generic-envs.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret101",
AllowEnv: true,
},
},
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret102",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, envvars, proceed, err := plugin.Extract(buildConfig, "secret101", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' to return 'true'")
}
if revision == nil {
t.Errorf("Expected the 'revision' to not be nil")
}
if len(envvars) == 0 {
t.Error("Expected env vars to be set")
}
}
func TestWrongSecret(t *testing.T) {
req := GivenRequest("POST")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "wrongsecret", "", req)
if err != webhook.ErrSecretMismatch {
t.Errorf("Expected %v, got %v!", webhook.ErrSecretMismatch, err)
}
if proceed {
t.Error("Expected 'proceed' return value to be 'false'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
type emptyReader struct{}
func (_ emptyReader) Read(p []byte) (n int, err error) {
return 0, io.EOF
}
func TestExtractWithEmptyPayload(t *testing.T) {
req, _ := http.NewRequest("POST", "http://someurl.com", emptyReader{})
req.Header.Add("Content-Type", "application/json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
func TestExtractWithUnmatchedRefGitPayload(t *testing.T) {
req := GivenRequestWithPayload(t, "push-generic.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "asdfkasdfasdfasdfadsfkjhkhkh",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
build, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
matchWarning(t, err, `skipping build. Branch reference from "refs/heads/master" does not match configuration`)
if proceed {
t.Error("Expected 'proceed' return value to be 'false' for unmatched refs")
}
if build != nil {
t.Error("Expected the 'revision' return value to be nil since we aren't creating a new one")
}
}
func TestExtractWithGitPayload(t *testing.T) {
req := GivenRequestWithPayload(t, "push-generic.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision == nil {
t.Error("Expected the 'revision' return value to not be nil")
}
}
func TestExtractWithGitPayloadAndUTF8Charset(t *testing.T) {
req := GivenRequestWithPayloadAndContentType(t, "push-generic.json", "application/json; charset=utf-8")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision == nil {
t.Error("Expected the 'revision' return value to not be nil")
}
}
func TestExtractWithGitRefsPayload(t *testing.T) {
req := GivenRequestWithRefsPayload(t)
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision == nil {
t.Error("Expected the 'revision' return value to not be nil")
}
}
func TestExtractWithUnmatchedGitRefsPayload(t *testing.T) {
req := GivenRequestWithRefsPayload(t)
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "other",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
matchWarning(t, err, `skipping build. None of the supplied refs matched "other"`)
if proceed {
t.Error("Expected 'proceed' return value to be 'false'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
func TestExtractWithKeyValuePairsJSON(t *testing.T) {
req := GivenRequestWithPayload(t, "push-generic-envs.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
AllowEnv: true,
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, envvars, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision == nil {
t.Error("Expected the 'revision' return value to not be nil")
}
if len(envvars) == 0 {
t.Error("Expected env vars to be set")
}
}
func TestExtractWithKeyValuePairsYAML(t *testing.T) {
req := GivenRequestWithPayloadAndContentType(t, "push-generic-envs.yaml", "application/yaml")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
AllowEnv: true,
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, envvars, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision == nil {
t.Error("Expected the 'revision' return value to not be nil")
}
if len(envvars) == 0 {
t.Error("Expected env vars to be set")
}
}
func TestExtractWithKeyValuePairsDisabled(t *testing.T) {
req := GivenRequestWithPayload(t, "push-generic-envs.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "master",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, envvars, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision == nil {
t.Error("Expected the 'revision' return value to not be nil")
}
if len(envvars) != 0 {
t.Error("Expected env vars to be empty")
}
}
func TestGitlabPush(t *testing.T) {
req := GivenRequestWithPayload(t, "push-gitlab.json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
_, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
matchWarning(t, err, "no git information found in payload, ignoring and continuing with build")
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
}
func TestNonJsonPush(t *testing.T) {
req, _ := http.NewRequest("POST", "http://someurl.com", nil)
req.Header.Add("Content-Type", "*/*")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
_, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err != nil {
t.Errorf("Expected to be able to trigger a build without a payload error: %v", err)
}
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
}
type errJSON struct{}
func (_ errJSON) Read(p []byte) (n int, err error) {
p = []byte("{")
return len(p), io.EOF
}
func TestExtractWithUnmarshalError(t *testing.T) {
req, _ := http.NewRequest("POST", "http://someurl.com", errJSON{})
req.Header.Add("Content-Type", "application/json")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "other",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
matchWarning(t, err, `error unmarshalling payload: invalid character '\x00' looking for beginning of value, ignoring payload and continuing with build`)
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
func TestExtractWithUnmarshalErrorYAML(t *testing.T) {
req, _ := http.NewRequest("POST", "http://someurl.com", errJSON{})
req.Header.Add("Content-Type", "application/yaml")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "other",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
matchWarning(t, err, "error converting payload to json: yaml: control characters are not allowed, ignoring payload and continuing with build")
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
func TestExtractWithBadContentType(t *testing.T) {
req, _ := http.NewRequest("POST", "http://someurl.com", errJSON{})
req.Header.Add("Content-Type", "bad")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "other",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
matchWarning(t, err, "invalid Content-Type on payload, ignoring payload and continuing with build")
if !proceed {
t.Error("Expected 'proceed' return value to be 'true'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}
func TestExtractWithUnparseableContentType(t *testing.T) {
req, _ := http.NewRequest("POST", "http://someurl.com", errJSON{})
req.Header.Add("Content-Type", "bad//bad")
buildConfig := &api.BuildConfig{
Spec: api.BuildConfigSpec{
Triggers: []api.BuildTriggerPolicy{
{
Type: api.GenericWebHookBuildTriggerType,
GenericWebHook: &api.WebHookTrigger{
Secret: "secret100",
},
},
},
CommonSpec: api.CommonSpec{
Source: api.BuildSource{
Git: &api.GitBuildSource{
Ref: "other",
},
},
Strategy: mockBuildStrategy,
},
},
}
plugin := New()
revision, _, proceed, err := plugin.Extract(buildConfig, "secret100", "", req)
if err == nil || err.Error() != "error parsing Content-Type: mime: expected token after slash" {
t.Errorf("Unexpected error %v", err)
}
if proceed {
t.Error("Expected 'proceed' return value to be 'false'")
}
if revision != nil {
t.Error("Expected the 'revision' return value to be nil")
}
}