<commit_before>package web_test

import (
	"net/http"
	"testing"

	"net/http/httptest"

	"bytes"
	"encoding/json"

	"io/ioutil"

	"fmt"

	"os"

	"mime/multipart"

	"github.com/Sirupsen/logrus"
	"github.com/feedhenry/mcp-standalone/pkg/data"
	"github.com/feedhenry/mcp-standalone/pkg/mobile"
	"github.com/feedhenry/mcp-standalone/pkg/mobile/app"
	"github.com/feedhenry/mcp-standalone/pkg/mock"
	"github.com/feedhenry/mcp-standalone/pkg/web"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
	kfake "k8s.io/client-go/testing"
)

func setupMobileBuildHandler(kclient kubernetes.Interface, ocFake *kfake.Fake) http.Handler {
	r := web.NewRouter()
	logger := logrus.StandardLogger()
	if nil == kclient {
		kclient = &fake.Clientset{}
	}

	cb := &mock.ClientBuilder{
		Fakeclient: kclient,
	}
	ocClientBuilder := mock.NewOCClientBuilder("test", "test", "https://notthere.com", ocFake)
	repoBuilder := data.NewBuildsRepoBuilder(cb, ocClientBuilder, "test", "test")
	buildService := app.NewBuild()
	handler := web.NewBuildHandler(repoBuilder, buildService, logger)
	web.MobileBuildRoute(r, handler)
	return web.BuildHTTPHandler(r, nil)
}

func TestBuildHandlerCreate(t *testing.T) {
	cases := []struct {
		Name        string
		K8Client    func() kubernetes.Interface
		OCClient    func() *kfake.Fake
		ExpectError bool
		StatusCode  int
		MobileBuild *mobile.Build
		Validate    func(t *testing.T, ar *app.AppBuildCreatedResponse)
	}{
		{
			Name:       "test build create for private repo ok",
			StatusCode: 201,
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {

				c := &kfake.Fake{}
				c.AddReactor("create", "buildconfig", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			MobileBuild: &mobile.Build{
				Name:  "mybuild",
				AppID: "myapp",
				GitRepo: &mobile.BuildGitRepo{
					Private: true,
					URI:     "git@git.com",
					Ref:     "master",
				},
			},
			Validate: func(t *testing.T, ar *app.AppBuildCreatedResponse) {
				if nil == ar {
					t.Fatal("expected a build creation response but got none")
				}
				if ar.PublicKey == "" {
					t.Fatal("expected a public key in the response but got none")
				}
				if ar.BuildID != "mybuild" {
					t.Fatalf("expected a build id to match : mybuild but got %s", ar.BuildID)
				}
			},
		},
		{
			Name:       "test build create for public repo ok",
			StatusCode: 201,
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {

				c := &kfake.Fake{}
				c.AddReactor("create", "buildconfig", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			MobileBuild: &mobile.Build{
				Name:  "mybuild",
				AppID: "myapp",
				GitRepo: &mobile.BuildGitRepo{
					URI: "git@git.com",
					Ref: "master",
				},
			},
			Validate: func(t *testing.T, ar *app.AppBuildCreatedResponse) {
				if nil == ar {
					t.Fatal("expected a build creation response but got none")
				}
				if ar.PublicKey != "" {
					t.Fatal("did not expect a public key in the response but got one")
				}
				if ar.BuildID != "mybuild" {
					t.Fatalf("expected a build id to match : mybuild but got %s", ar.BuildID)
				}
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			handler := setupMobileBuildHandler(tc.K8Client(), tc.OCClient())
			server := httptest.NewServer(handler)
			defer server.Close()
			payload, err := json.Marshal(tc.MobileBuild)
			if err != nil {
				t.Fatal("failed to marshal json payload")
			}
			req, err := http.NewRequest("POST", server.URL+"/build", bytes.NewReader(payload))
			if err != nil {
				t.Fatalf("did not expect an error setting up request %s", err)
			}
			res, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("did not expect an error making request %s", err)
			}
			defer res.Body.Close()
			if res.StatusCode != tc.StatusCode {
				t.Fatalf("expected status code %v but got %v ", tc.StatusCode, res.StatusCode)
			}
			if res.StatusCode == 201 {
				resBody, err := ioutil.ReadAll(res.Body)
				if err != nil {
					t.Fatalf("failed to read the response body %s", err)
				}
				response := &app.AppBuildCreatedResponse{}
				if err := json.Unmarshal(resBody, response); err != nil {
					t.Fatalf("did not expect an error unmarshalling the response body %s ", err)
				}
			}
		})
	}
}

func TestBuildHandlerGenerateKeys(t *testing.T) {
	cases := []struct {
		Name        string
		BuildID     string
		K8Client    func() kubernetes.Interface
		OCClient    func() *kfake.Fake
		ExpectError bool
		StatusCode  int
		Validate    func(res map[string]string, t *testing.T)
	}{
		{
			Name:    "test generate new keys ok",
			BuildID: "testbuild",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			StatusCode: 201,
			Validate: func(res map[string]string, t *testing.T) {
				if res == nil {
					t.Fatal("expected a response body but got none")
				}
				if _, ok := res["name"]; !ok {
					t.Fatal("expected a name to be returned in the response")
				}
			},
		},
		{
			Name:    "test generate new keys fails if no buildID",
			BuildID: "",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			StatusCode: 404,
		},
		{
			Name:    "test generate new keys fails if secret already exists",
			BuildID: "test",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					return true, nil, errors.NewConflict(schema.GroupResource{Resource: "", Group: ""}, "test", fmt.Errorf("this secret already exists "))
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			StatusCode: 409,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			handler := setupMobileBuildHandler(tc.K8Client(), tc.OCClient())
			server := httptest.NewServer(handler)
			defer server.Close()
			req, err := http.NewRequest("POST", server.URL+"/build/"+tc.BuildID+"/generatekeys", nil)
			if err != nil {
				t.Fatalf("did not expect an error setting up request %s", err)
			}
			res, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("did not expect an error making request %s", err)
			}
			defer res.Body.Close()
			if res.StatusCode != tc.StatusCode {
				t.Fatalf("expected status code %v but got %v ", tc.StatusCode, res.StatusCode)
			}
			if res.StatusCode == 201 {
				resBod := map[string]string{}
				data, err := ioutil.ReadAll(res.Body)
				if err != nil {
					t.Fatal("failed to read response body ", err)
				}
				if err := json.Unmarshal(data, &resBod); err != nil {
					t.Fatal("failed to unmarshal response body", err)
				}
				if tc.Validate != nil {
					tc.Validate(resBod, t)
				}
			}

		})
	}
}

func TestBuildHandlerAddAsset(t *testing.T) {
	cases := []struct {
		Name       string
		K8Client   func() kubernetes.Interface
		OCClient   func() *kfake.Fake
		Platform   string
		Params     map[string]string
		StatusCode int
	}{
		{
			Name: "test adding new build asset succeeds",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			Platform:   "android",
			Params:     map[string]string{"platform": "android", "path": "/etc/resource"},
			StatusCode: 201,
		},
		{
			Name: "test adding new build asset fails when invalid",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			Platform:   "none",
			Params:     map[string]string{},
			StatusCode: 400,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			handler := setupMobileBuildHandler(tc.K8Client(), tc.OCClient())
			server := httptest.NewServer(handler)
			defer server.Close()
			req, err := newUploadFileRequest(server.URL+"/build/platform/"+tc.Platform+"/assets", tc.Params, "asset", "../../server.crt")
			if err != nil {
				t.Fatal("unexpected error creating new file upload request ", err)
			}
			res, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal("unexpected error making asset upload request", err)
			}

			if res.StatusCode != tc.StatusCode {
				t.Fatalf("expected status code %v but got %v ", tc.StatusCode, res.StatusCode)
			}

		})
	}
}

func newUploadFileRequest(endpoint string, formValues map[string]string, formFileField, filePath string) (*http.Request, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	fileContents, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}
	fi, err := file.Stat()
	if err != nil {
		return nil, err
	}
	defer file.Close()
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(formFileField, fi.Name())
	if err != nil {
		return nil, err
	}
	_, err = part.Write(fileContents)
	if err != nil {
		return nil, err
	}

	for key, val := range formValues {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", endpoint, body)
	req.Header.Set("content-type", writer.FormDataContentType())
	return req, err
}
<commit_msg>improve test<commit_after>package web_test

import (
	"net/http"
	"testing"

	"net/http/httptest"

	"bytes"
	"encoding/json"

	"io/ioutil"

	"fmt"

	"os"

	"mime/multipart"

	"github.com/Sirupsen/logrus"
	"github.com/feedhenry/mcp-standalone/pkg/data"
	"github.com/feedhenry/mcp-standalone/pkg/mobile"
	"github.com/feedhenry/mcp-standalone/pkg/mobile/app"
	"github.com/feedhenry/mcp-standalone/pkg/mock"
	"github.com/feedhenry/mcp-standalone/pkg/web"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/pkg/api/v1"
	kfake "k8s.io/client-go/testing"
)

func setupMobileBuildHandler(kclient kubernetes.Interface, ocFake *kfake.Fake) http.Handler {
	r := web.NewRouter()
	logger := logrus.StandardLogger()
	if nil == kclient {
		kclient = &fake.Clientset{}
	}

	cb := &mock.ClientBuilder{
		Fakeclient: kclient,
	}
	ocClientBuilder := mock.NewOCClientBuilder("test", "test", "https://notthere.com", ocFake)
	repoBuilder := data.NewBuildsRepoBuilder(cb, ocClientBuilder, "test", "test")
	buildService := app.NewBuild()
	handler := web.NewBuildHandler(repoBuilder, buildService, logger)
	web.MobileBuildRoute(r, handler)
	return web.BuildHTTPHandler(r, nil)
}

func TestBuildHandlerCreate(t *testing.T) {
	cases := []struct {
		Name        string
		K8Client    func() kubernetes.Interface
		OCClient    func() *kfake.Fake
		ExpectError bool
		StatusCode  int
		MobileBuild *mobile.Build
		Validate    func(t *testing.T, ar *app.AppBuildCreatedResponse)
	}{
		{
			Name:       "test build create for private repo ok",
			StatusCode: 201,
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {

				c := &kfake.Fake{}
				c.AddReactor("create", "buildconfig", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			MobileBuild: &mobile.Build{
				Name:  "mybuild",
				AppID: "myapp",
				GitRepo: &mobile.BuildGitRepo{
					Private: true,
					URI:     "git@git.com",
					Ref:     "master",
				},
			},
			Validate: func(t *testing.T, ar *app.AppBuildCreatedResponse) {
				if nil == ar {
					t.Fatal("expected a build creation response but got none")
				}
				if ar.PublicKey == "" {
					t.Fatal("expected a public key in the response but got none")
				}
				if ar.BuildID != "mybuild" {
					t.Fatalf("expected a build id to match : mybuild but got %s", ar.BuildID)
				}
			},
		},
		{
			Name:       "test build create for public repo ok",
			StatusCode: 201,
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {

				c := &kfake.Fake{}
				c.AddReactor("create", "buildconfig", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			MobileBuild: &mobile.Build{
				Name:  "mybuild",
				AppID: "myapp",
				GitRepo: &mobile.BuildGitRepo{
					URI: "git@git.com",
					Ref: "master",
				},
			},
			Validate: func(t *testing.T, ar *app.AppBuildCreatedResponse) {
				if nil == ar {
					t.Fatal("expected a build creation response but got none")
				}
				if ar.PublicKey != "" {
					t.Fatal("did not expect a public key in the response but got one")
				}
				if ar.BuildID != "mybuild" {
					t.Fatalf("expected a build id to match : mybuild but got %s", ar.BuildID)
				}
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			handler := setupMobileBuildHandler(tc.K8Client(), tc.OCClient())
			server := httptest.NewServer(handler)
			defer server.Close()
			payload, err := json.Marshal(tc.MobileBuild)
			if err != nil {
				t.Fatal("failed to marshal json payload")
			}
			req, err := http.NewRequest("POST", server.URL+"/build", bytes.NewReader(payload))
			if err != nil {
				t.Fatalf("did not expect an error setting up request %s", err)
			}
			res, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("did not expect an error making request %s", err)
			}
			defer res.Body.Close()
			if res.StatusCode != tc.StatusCode {
				t.Fatalf("expected status code %v but got %v ", tc.StatusCode, res.StatusCode)
			}
			if res.StatusCode == 201 {
				resBody, err := ioutil.ReadAll(res.Body)
				if err != nil {
					t.Fatalf("failed to read the response body %s", err)
				}
				response := &app.AppBuildCreatedResponse{}
				if err := json.Unmarshal(resBody, response); err != nil {
					t.Fatalf("did not expect an error unmarshalling the response body %s ", err)
				}
			}
		})
	}
}

func TestBuildHandlerGenerateKeys(t *testing.T) {
	cases := []struct {
		Name        string
		BuildID     string
		K8Client    func() kubernetes.Interface
		OCClient    func() *kfake.Fake
		ExpectError bool
		StatusCode  int
		Validate    func(res map[string]string, t *testing.T)
	}{
		{
			Name:    "test generate new keys ok",
			BuildID: "testbuild",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			StatusCode: 201,
			Validate: func(res map[string]string, t *testing.T) {
				if res == nil {
					t.Fatal("expected a response body but got none")
				}
				if _, ok := res["name"]; !ok {
					t.Fatal("expected a name to be returned in the response")
				}
			},
		},
		{
			Name:    "test generate new keys fails if no buildID",
			BuildID: "",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			StatusCode: 404,
		},
		{
			Name:    "test generate new keys fails if secret already exists",
			BuildID: "test",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					return true, nil, errors.NewConflict(schema.GroupResource{Resource: "", Group: ""}, "test", fmt.Errorf("this secret already exists "))
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			StatusCode: 409,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			handler := setupMobileBuildHandler(tc.K8Client(), tc.OCClient())
			server := httptest.NewServer(handler)
			defer server.Close()
			req, err := http.NewRequest("POST", server.URL+"/build/"+tc.BuildID+"/generatekeys", nil)
			if err != nil {
				t.Fatalf("did not expect an error setting up request %s", err)
			}
			res, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("did not expect an error making request %s", err)
			}
			defer res.Body.Close()
			if res.StatusCode != tc.StatusCode {
				t.Fatalf("expected status code %v but got %v ", tc.StatusCode, res.StatusCode)
			}
			if res.StatusCode == 201 {
				resBod := map[string]string{}
				data, err := ioutil.ReadAll(res.Body)
				if err != nil {
					t.Fatal("failed to read response body ", err)
				}
				if err := json.Unmarshal(data, &resBod); err != nil {
					t.Fatal("failed to unmarshal response body", err)
				}
				if tc.Validate != nil {
					tc.Validate(resBod, t)
				}
			}

		})
	}
}

func TestBuildHandlerAddAsset(t *testing.T) {
	cases := []struct {
		Name       string
		K8Client   func() kubernetes.Interface
		OCClient   func() *kfake.Fake
		Platform   string
		Params     map[string]string
		StatusCode int
	}{
		{
			Name: "test adding new build asset succeeds",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject().(*v1.Secret)
					if _, ok := obj.Data["server.crt"]; !ok {
						t.Fatalf("expected to find the server key but it was not present")
					}
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			Platform:   "android",
			Params:     map[string]string{"platform": "android", "path": "/etc/resource"},
			StatusCode: 201,
		},
		{
			Name: "test adding new build asset fails when invalid",
			K8Client: func() kubernetes.Interface {
				c := &fake.Clientset{}
				c.AddReactor("create", "secrets", func(action kfake.Action) (handled bool, ret runtime.Object, err error) {
					obj := action.(kfake.CreateAction).GetObject()
					return true, obj, nil
				})
				return c
			},
			OCClient: func() *kfake.Fake {
				c := &kfake.Fake{}
				return c
			},
			Platform:   "none",
			Params:     map[string]string{},
			StatusCode: 400,
		},
	}

	for _, tc := range cases {
		t.Run(tc.Name, func(t *testing.T) {
			handler := setupMobileBuildHandler(tc.K8Client(), tc.OCClient())
			server := httptest.NewServer(handler)
			defer server.Close()
			req, err := newUploadFileRequest(server.URL+"/build/platform/"+tc.Platform+"/assets", tc.Params, "asset", "../../server.crt")
			if err != nil {
				t.Fatal("unexpected error creating new file upload request ", err)
			}
			res, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal("unexpected error making asset upload request", err)
			}

			if res.StatusCode != tc.StatusCode {
				t.Fatalf("expected status code %v but got %v ", tc.StatusCode, res.StatusCode)
			}

		})
	}
}

func newUploadFileRequest(endpoint string, formValues map[string]string, formFileField, filePath string) (*http.Request, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	fileContents, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}
	fi, err := file.Stat()
	if err != nil {
		return nil, err
	}
	defer file.Close()
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(formFileField, fi.Name())
	if err != nil {
		return nil, err
	}
	_, err = part.Write(fileContents)
	if err != nil {
		return nil, err
	}

	for key, val := range formValues {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", endpoint, body)
	req.Header.Set("content-type", writer.FormDataContentType())
	return req, err
}
<|endoftext|>


<commit_before>// Copyright 2018 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package awspubsub

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strings"
	"sync"
	"sync/atomic"
	"testing"

	gax "github.com/googleapis/gax-go"
	"gocloud.dev/internal/batcher"
	"gocloud.dev/internal/retry"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sqs"
	"gocloud.dev/internal/testing/setup"
	"gocloud.dev/pubsub"
	"gocloud.dev/pubsub/driver"
	"gocloud.dev/pubsub/drivertest"
)

const (
	region = "us-east-2"
	benchmarkTopicARN = "arn:aws:sns:us-east-2:221420415498:benchmark-topic"
	benchmarkSubscriptionURL = "https://sqs.us-east-2.amazonaws.com/221420415498/benchmark-queue"
)

type harness struct {
	sess      *session.Session
	cfg       *aws.Config
	rt        http.RoundTripper
	closer    func()
	numTopics uint32
	numSubs   uint32
}

func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
	sess, rt, done := setup.NewAWSSession(t, region)
	return &harness{sess: sess, cfg: &aws.Config{}, rt: rt, closer: done, numTopics: 0, numSubs: 0}, nil
}

func (h *harness) CreateTopic(ctx context.Context, testName string) (dt driver.Topic, cleanup func(), err error) {
	client := sns.New(h.sess, h.cfg)
	topicName := fmt.Sprintf("%s-topic-%d", sanitize(testName), atomic.AddUint32(&h.numTopics, 1))
	out, err := client.CreateTopic(&sns.CreateTopicInput{Name: aws.String(topicName)})
	if err != nil {
		return nil, nil, fmt.Errorf(`creating topic "%s": %v`, topicName, err)
	}
	dt = openTopic(ctx, client, *out.TopicArn)
	cleanup = func() {
		// TODO: Call client.DeleteTopic(&sns.DeleteTopicInput{TopicArn: out.TopicArn})
		// once https://github.com/aws/aws-sdk-go/issues/2415 is resolved.
	}
	return dt, cleanup, nil
}

func (h *harness) MakeNonexistentTopic(ctx context.Context) (driver.Topic, error) {
	client := sns.New(h.sess, h.cfg)
	dt := openTopic(ctx, client, "nonexistent-topic")
	return dt, nil
}

func (h *harness) CreateSubscription(ctx context.Context, dt driver.Topic, testName string) (ds driver.Subscription, cleanup func(), err error) {
	sqsClient := sqs.New(h.sess, h.cfg)
	subName := fmt.Sprintf("%s-subscription-%d", sanitize(testName), atomic.AddUint32(&h.numSubs, 1))
	out, err := sqsClient.CreateQueue(&sqs.CreateQueueInput{QueueName: aws.String(subName)})
	if err != nil {
		return nil, nil, fmt.Errorf(`creating subscription queue "%s": %v`, subName, err)
	}
	ds = openSubscription(ctx, sqsClient, *out.QueueUrl)

	// TODO: call
	//   snsClient := sns.New(h.sess, h.cfg)
	//   subscribeQueueToTopic(ctx, sqsClient, snsClient, out.QueueURL, dt)
	// once https://github.com/aws/aws-sdk-go/issues/2415 is resolved.
	//
	// In the meantime, it's necessary to manually go into the AWS console
	// in the SQS section and manually subscribe the queues to the topics
	// after running the test once in -record mode and seeing it fail due
	// to the queues not being subscribed.
	cleanup = func() {
		// TODO: Call sqsClient.DeleteQueue(&sqs.DeleteQueueInput{QueueUrl: out.QueueUrl})
		// once https://github.com/aws/aws-sdk-go/issues/2415 is resolved.
	}
	return ds, cleanup, nil
}

func (h *harness) ShouldSkip(testName string) (bool, string) {
	if !*setup.Record {
		if strings.Contains(testName, "TestSendReceive") {
			return true, "TestSendReceive* tests hang and panic in replay mode on awspubsub"
		}
		if strings.Contains(testName, "TestAs") {
			return true, "TestAs hangs in replay mode on awspubsub"
		}
	}
	return false, ""
}

// ackBatcher is a trivial batcher that sends off items as singleton batches.
type ackBatcher struct {
	handler func(items interface{}) error
}

func (ab *ackBatcher) Add(ctx context.Context, item interface{}) error {
	item2 := item.(driver.AckID)
	items := []driver.AckID{item2}
	return ab.handler(items)
}

func (ab *ackBatcher) AddNoWait(item interface{}) <-chan error {
	item2 := item.(driver.AckID)
	items := []driver.AckID{item2}
	c := make(chan error)
	go func() {
		c <- ab.handler(items)
	}()
	return c
}

func (ab *ackBatcher) Shutdown() {
}

func subscribeQueueToTopic(ctx context.Context, sqsClient *sqs.SQS, snsClient *sns.SNS, qURL *string, dt driver.Topic) error {
	out2, err := sqsClient.GetQueueAttributes(&sqs.GetQueueAttributesInput{
		QueueUrl:       qURL,
		AttributeNames: []*string{aws.String("QueueArn")},
	})
	if err != nil {
		return fmt.Errorf("getting queue ARN for %s: %v", *qURL, err)
	}
	qARN := out2.Attributes["QueueArn"]

	t := dt.(*topic)
	_, err = snsClient.Subscribe(&sns.SubscribeInput{
		TopicArn: aws.String(t.arn),
		Endpoint: qARN,
		Protocol: aws.String("sqs"),
	})
	if err != nil {
		return fmt.Errorf("subscribing: %v", err)
	}

	// Get the confirmation from the queue.
	out3, err := sqsClient.ReceiveMessage(&sqs.ReceiveMessageInput{
		QueueUrl: qURL,
	})
	if err != nil {
		return fmt.Errorf("receiving subscription confirmation message from queue: %v", err)
	}
	ms := out3.Messages
	var token *string
	switch len(ms) {
	case 0:
		return errors.New("no subscription confirmation message found in queue")
	case 1:
		m := ms[0]
		token = m.Body
	default:
		return fmt.Errorf("%d messages found in queue, want exactly 1", len(ms))
	}
	_, err = snsClient.ConfirmSubscription(&sns.ConfirmSubscriptionInput{
		TopicArn: aws.String(t.arn),
		Token:    token,
	})
	if err != nil {
		return fmt.Errorf("confirming subscription: %v", err)
	}
	return nil
}

func makeAckBatcher(ctx context.Context, ds driver.Subscription, setPermanentError func(error)) driver.Batcher {
	const maxHandlers = 1
	h := func(items interface{}) error {
		ids := items.([]driver.AckID)
		err := retry.Call(ctx, gax.Backoff{}, ds.IsRetryable, func() error {
			return ds.SendAcks(ctx, ids)
		})
		if err != nil {
			setPermanentError(err)
		}
		return err
	}
	b := batcher.New(reflect.TypeOf([]driver.AckID{}).Elem(), maxHandlers, h)
	return &wrappedBatcher{b}
	// return &simpleBatcher{handler: h, batch: nil}
}

type wrappedBatcher struct {
	b *batcher.Batcher
}

// Add adds an item to the batcher.
func (wb *wrappedBatcher) Add(ctx context.Context, item interface{}) error {
	return wb.b.Add(ctx, item)
}

// AddNoWait adds an item to the batcher. Unlike the method with the
// same name on the production batcher (internal/batcher), this method
// blocks in order to make acking and receiving happen in a deterministic
// order, to support record/replay.
func (wb *wrappedBatcher) AddNoWait(item interface{}) <-chan error {
	c := make(chan error, 1)
	defer close(c)
	c <- wb.b.Add(context.Background(), item)
	return c
}

// Shutdown waits for all active calls to Add to finish, then returns. After
// Shutdown is called, all calls to Add fail.
func (wb *wrappedBatcher) Shutdown() {
	wb.b.Shutdown()
}

type simpleBatcher struct {
	mu   sync.Mutex
	done bool

	handler func(items interface{}) error
}

// Add adds an item to the batcher.
func (sb *simpleBatcher) Add(ctx context.Context, item interface{}) error {
	c := sb.AddNoWait(item)
	// Wait until either our result is ready or the context is done.
	select {
	case err := <-c:
		return err
	case <-ctx.Done():
		return ctx.Err()
	}
}

// AddNoWait adds an item to the batcher. Unlike the method with the
// same name on the production batcher (internal/batcher), this method
// blocks in order to make acking and receiving happen in a deterministic
// order, to support record/replay.
func (sb *simpleBatcher) AddNoWait(item interface{}) <-chan error {
	c := make(chan error, 1)
	defer close(c)
	if sb.done {
		c <- errors.New("tried to add an item to a simpleBatcher after shutdown")
		return c
	}
	m := item.(*pubsub.Message)
	batch := []*pubsub.Message{m}
	err := sb.handler(batch)
	if err != nil {
		c <- err
	}
	return c
}

// Shutdown waits for all active calls to Add to finish, then returns. After
// Shutdown is called, all calls to Add fail.
func (sb *simpleBatcher) Shutdown() {
	sb.mu.Lock()
	defer sb.mu.Unlock()
	sb.done = true
}

func (h *harness) MakeNonexistentSubscription(ctx context.Context) (driver.Subscription, error) {
	client := sqs.New(h.sess, h.cfg)
	ds := openSubscription(ctx, client, "nonexistent-subscription")
	return ds, nil
}

func (h *harness) Close() {
	h.closer()
}

func TestConformance(t *testing.T) {
	asTests := []drivertest.AsTest{awsAsTest{}}
	drivertest.RunConformanceTests(t, newHarness, asTests)
}

type awsAsTest struct{}

func (awsAsTest) Name() string {
	return "aws test"
}

func (awsAsTest) TopicCheck(top *pubsub.Topic) error {
	var s *sns.SNS
	if !top.As(&s) {
		return fmt.Errorf("cast failed for %T", s)
	}
	return nil
}

func (awsAsTest) SubscriptionCheck(sub *pubsub.Subscription) error {
	var s *sqs.SQS
	if !sub.As(&s) {
		return fmt.Errorf("cast failed for %T", s)
	}
	return nil
}

func (awsAsTest) ErrorCheck(t *pubsub.Topic, err error) error {
	var ae awserr.Error
	if !t.ErrorAs(err, &ae) {
		return fmt.Errorf("failed to convert %v (%T) to an awserr.Error", err, err)
	}
	// It seems like it should be ErrCodeNotFoundException but that's not what AWS gives back.
	if ae.Code() != sns.ErrCodeInvalidParameterException {
		return fmt.Errorf("got %q, want %q", ae.Code(), sns.ErrCodeInvalidParameterException)
	}
	return nil
}

func (awsAsTest) MessageCheck(m *pubsub.Message) error {
	var sm sqs.Message
	if m.As(&sm) {
		return fmt.Errorf("cast succeeded for %T, want failure", &sm)
	}
	var psm *sqs.Message
	if !m.As(&psm) {
		return fmt.Errorf("cast failed for %T", &psm)
	}
	return nil
}

func sanitize(testName string) string {
	return strings.Replace(testName, "/", "_", -1)
}

func BenchmarkAwsPubSub(b *testing.B) {
	ctx := context.Background()
	sess, err := session.NewSession(&aws.Config{
		HTTPClient:  &http.Client{},
		Region:      aws.String(region),
		MaxRetries:  aws.Int(0),
	})
	if err != nil {
		b.Fatal(err)
	}
	snsClient := sns.New(sess, &aws.Config{})
	sqsClient := sqs.New(sess, &aws.Config{})
	topic := OpenTopic(ctx, snsClient, benchmarkTopicARN, nil)
	sub := OpenSubscription(ctx, sqsClient, benchmarkSubscriptionURL, nil)
	drivertest.RunBenchmarks(b, topic, sub)
}
<commit_msg>pubsub: add TestOpenTopic, TestOpenSubscription to awspubsub_test (#1249)<commit_after>// Copyright 2018 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package awspubsub

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strings"
	"sync"
	"sync/atomic"
	"testing"

	"github.com/googleapis/gax-go"
	"gocloud.dev/internal/batcher"
	"gocloud.dev/internal/retry"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sqs"
	"gocloud.dev/internal/testing/setup"
	"gocloud.dev/pubsub"
	"gocloud.dev/pubsub/driver"
	"gocloud.dev/pubsub/drivertest"
)

const (
	region = "us-east-2"
	benchmarkTopicARN = "arn:aws:sns:us-east-2:221420415498:benchmark-topic"
	benchmarkSubscriptionURL = "https://sqs.us-east-2.amazonaws.com/221420415498/benchmark-queue"
)

func TestOpenTopic(t *testing.T) {
	ctx := context.Background()
	sess, err := newSession()
	if err != nil {
		t.Fatal(err)
	}
	client := sns.New(sess, &aws.Config{})
	fakeTopicARN := ""
	topic := OpenTopic(ctx, client, fakeTopicARN, nil)
	if err := topic.Send(ctx, &pubsub.Message{Body: []byte("")}); err == nil {
		t.Error("got nil, want error from send to nonexistent topic")
	}
}

func TestOpenSubscription(t *testing.T) {
	ctx := context.Background()
	sess, err := newSession()
	if err != nil {
		t.Fatal(err)
	}
	client := sqs.New(sess, &aws.Config{})
	fakeQURL := ""
	sub := OpenSubscription(ctx, client, fakeQURL, nil)
	if _, err := sub.Receive(ctx); err == nil {
		t.Error("got nil, want error from receive from nonexistent subscription")
	}
}

func newSession() (*session.Session, error) {
	return session.NewSession(&aws.Config{
		HTTPClient: &http.Client{},
		Region:     aws.String(region),
		MaxRetries: aws.Int(0),
	})
}

type harness struct {
	sess      *session.Session
	cfg       *aws.Config
	rt        http.RoundTripper
	closer    func()
	numTopics uint32
	numSubs   uint32
}

func newHarness(ctx context.Context, t *testing.T) (drivertest.Harness, error) {
	sess, rt, done := setup.NewAWSSession(t, region)
	return &harness{sess: sess, cfg: &aws.Config{}, rt: rt, closer: done, numTopics: 0, numSubs: 0}, nil
}

func (h *harness) CreateTopic(ctx context.Context, testName string) (dt driver.Topic, cleanup func(), err error) {
	client := sns.New(h.sess, h.cfg)
	topicName := fmt.Sprintf("%s-topic-%d", sanitize(testName), atomic.AddUint32(&h.numTopics, 1))
	out, err := client.CreateTopic(&sns.CreateTopicInput{Name: aws.String(topicName)})
	if err != nil {
		return nil, nil, fmt.Errorf(`creating topic "%s": %v`, topicName, err)
	}
	dt = openTopic(ctx, client, *out.TopicArn)
	cleanup = func() {
		// TODO: Call client.DeleteTopic(&sns.DeleteTopicInput{TopicArn: out.TopicArn})
		// once https://github.com/aws/aws-sdk-go/issues/2415 is resolved.
	}
	return dt, cleanup, nil
}

func (h *harness) MakeNonexistentTopic(ctx context.Context) (driver.Topic, error) {
	client := sns.New(h.sess, h.cfg)
	dt := openTopic(ctx, client, "nonexistent-topic")
	return dt, nil
}

func (h *harness) CreateSubscription(ctx context.Context, dt driver.Topic, testName string) (ds driver.Subscription, cleanup func(), err error) {
	sqsClient := sqs.New(h.sess, h.cfg)
	subName := fmt.Sprintf("%s-subscription-%d", sanitize(testName), atomic.AddUint32(&h.numSubs, 1))
	out, err := sqsClient.CreateQueue(&sqs.CreateQueueInput{QueueName: aws.String(subName)})
	if err != nil {
		return nil, nil, fmt.Errorf(`creating subscription queue "%s": %v`, subName, err)
	}
	ds = openSubscription(ctx, sqsClient, *out.QueueUrl)

	// TODO: call
	//   snsClient := sns.New(h.sess, h.cfg)
	//   subscribeQueueToTopic(ctx, sqsClient, snsClient, out.QueueURL, dt)
	// once https://github.com/aws/aws-sdk-go/issues/2415 is resolved.
	//
	// In the meantime, it's necessary to manually go into the AWS console
	// in the SQS section and manually subscribe the queues to the topics
	// after running the test once in -record mode and seeing it fail due
	// to the queues not being subscribed.
	cleanup = func() {
		// TODO: Call sqsClient.DeleteQueue(&sqs.DeleteQueueInput{QueueUrl: out.QueueUrl})
		// once https://github.com/aws/aws-sdk-go/issues/2415 is resolved.
	}
	return ds, cleanup, nil
}

func (h *harness) ShouldSkip(testName string) (bool, string) {
	if !*setup.Record {
		if strings.Contains(testName, "TestSendReceive") {
			return true, "TestSendReceive* tests hang and panic in replay mode on awspubsub"
		}
		if strings.Contains(testName, "TestAs") {
			return true, "TestAs hangs in replay mode on awspubsub"
		}
	}
	return false, ""
}

// ackBatcher is a trivial batcher that sends off items as singleton batches.
type ackBatcher struct {
	handler func(items interface{}) error
}

func (ab *ackBatcher) Add(ctx context.Context, item interface{}) error {
	item2 := item.(driver.AckID)
	items := []driver.AckID{item2}
	return ab.handler(items)
}

func (ab *ackBatcher) AddNoWait(item interface{}) <-chan error {
	item2 := item.(driver.AckID)
	items := []driver.AckID{item2}
	c := make(chan error)
	go func() {
		c <- ab.handler(items)
	}()
	return c
}

func (ab *ackBatcher) Shutdown() {
}

func subscribeQueueToTopic(ctx context.Context, sqsClient *sqs.SQS, snsClient *sns.SNS, qURL *string, dt driver.Topic) error {
	out2, err := sqsClient.GetQueueAttributes(&sqs.GetQueueAttributesInput{
		QueueUrl:       qURL,
		AttributeNames: []*string{aws.String("QueueArn")},
	})
	if err != nil {
		return fmt.Errorf("getting queue ARN for %s: %v", *qURL, err)
	}
	qARN := out2.Attributes["QueueArn"]

	t := dt.(*topic)
	_, err = snsClient.Subscribe(&sns.SubscribeInput{
		TopicArn: aws.String(t.arn),
		Endpoint: qARN,
		Protocol: aws.String("sqs"),
	})
	if err != nil {
		return fmt.Errorf("subscribing: %v", err)
	}

	// Get the confirmation from the queue.
	out3, err := sqsClient.ReceiveMessage(&sqs.ReceiveMessageInput{
		QueueUrl: qURL,
	})
	if err != nil {
		return fmt.Errorf("receiving subscription confirmation message from queue: %v", err)
	}
	ms := out3.Messages
	var token *string
	switch len(ms) {
	case 0:
		return errors.New("no subscription confirmation message found in queue")
	case 1:
		m := ms[0]
		token = m.Body
	default:
		return fmt.Errorf("%d messages found in queue, want exactly 1", len(ms))
	}
	_, err = snsClient.ConfirmSubscription(&sns.ConfirmSubscriptionInput{
		TopicArn: aws.String(t.arn),
		Token:    token,
	})
	if err != nil {
		return fmt.Errorf("confirming subscription: %v", err)
	}
	return nil
}

func makeAckBatcher(ctx context.Context, ds driver.Subscription, setPermanentError func(error)) driver.Batcher {
	const maxHandlers = 1
	h := func(items interface{}) error {
		ids := items.([]driver.AckID)
		err := retry.Call(ctx, gax.Backoff{}, ds.IsRetryable, func() error {
			return ds.SendAcks(ctx, ids)
		})
		if err != nil {
			setPermanentError(err)
		}
		return err
	}
	b := batcher.New(reflect.TypeOf([]driver.AckID{}).Elem(), maxHandlers, h)
	return &wrappedBatcher{b}
	// return &simpleBatcher{handler: h, batch: nil}
}

type wrappedBatcher struct {
	b *batcher.Batcher
}

// Add adds an item to the batcher.
func (wb *wrappedBatcher) Add(ctx context.Context, item interface{}) error {
	return wb.b.Add(ctx, item)
}

// AddNoWait adds an item to the batcher. Unlike the method with the
// same name on the production batcher (internal/batcher), this method
// blocks in order to make acking and receiving happen in a deterministic
// order, to support record/replay.
func (wb *wrappedBatcher) AddNoWait(item interface{}) <-chan error {
	c := make(chan error, 1)
	defer close(c)
	c <- wb.b.Add(context.Background(), item)
	return c
}

// Shutdown waits for all active calls to Add to finish, then returns. After
// Shutdown is called, all calls to Add fail.
func (wb *wrappedBatcher) Shutdown() {
	wb.b.Shutdown()
}

type simpleBatcher struct {
	mu   sync.Mutex
	done bool

	handler func(items interface{}) error
}

// Add adds an item to the batcher.
func (sb *simpleBatcher) Add(ctx context.Context, item interface{}) error {
	c := sb.AddNoWait(item)
	// Wait until either our result is ready or the context is done.
	select {
	case err := <-c:
		return err
	case <-ctx.Done():
		return ctx.Err()
	}
}

// AddNoWait adds an item to the batcher. Unlike the method with the
// same name on the production batcher (internal/batcher), this method
// blocks in order to make acking and receiving happen in a deterministic
// order, to support record/replay.
func (sb *simpleBatcher) AddNoWait(item interface{}) <-chan error {
	c := make(chan error, 1)
	defer close(c)
	if sb.done {
		c <- errors.New("tried to add an item to a simpleBatcher after shutdown")
		return c
	}
	m := item.(*pubsub.Message)
	batch := []*pubsub.Message{m}
	err := sb.handler(batch)
	if err != nil {
		c <- err
	}
	return c
}

// Shutdown waits for all active calls to Add to finish, then returns. After
// Shutdown is called, all calls to Add fail.
func (sb *simpleBatcher) Shutdown() {
	sb.mu.Lock()
	defer sb.mu.Unlock()
	sb.done = true
}

func (h *harness) MakeNonexistentSubscription(ctx context.Context) (driver.Subscription, error) {
	client := sqs.New(h.sess, h.cfg)
	ds := openSubscription(ctx, client, "nonexistent-subscription")
	return ds, nil
}

func (h *harness) Close() {
	h.closer()
}

func TestConformance(t *testing.T) {
	asTests := []drivertest.AsTest{awsAsTest{}}
	drivertest.RunConformanceTests(t, newHarness, asTests)
}

type awsAsTest struct{}

func (awsAsTest) Name() string {
	return "aws test"
}

func (awsAsTest) TopicCheck(top *pubsub.Topic) error {
	var s *sns.SNS
	if !top.As(&s) {
		return fmt.Errorf("cast failed for %T", s)
	}
	return nil
}

func (awsAsTest) SubscriptionCheck(sub *pubsub.Subscription) error {
	var s *sqs.SQS
	if !sub.As(&s) {
		return fmt.Errorf("cast failed for %T", s)
	}
	return nil
}

func (awsAsTest) ErrorCheck(t *pubsub.Topic, err error) error {
	var ae awserr.Error
	if !t.ErrorAs(err, &ae) {
		return fmt.Errorf("failed to convert %v (%T) to an awserr.Error", err, err)
	}
	// It seems like it should be ErrCodeNotFoundException but that's not what AWS gives back.
	if ae.Code() != sns.ErrCodeInvalidParameterException {
		return fmt.Errorf("got %q, want %q", ae.Code(), sns.ErrCodeInvalidParameterException)
	}
	return nil
}

func (awsAsTest) MessageCheck(m *pubsub.Message) error {
	var sm sqs.Message
	if m.As(&sm) {
		return fmt.Errorf("cast succeeded for %T, want failure", &sm)
	}
	var psm *sqs.Message
	if !m.As(&psm) {
		return fmt.Errorf("cast failed for %T", &psm)
	}
	return nil
}

func sanitize(testName string) string {
	return strings.Replace(testName, "/", "_", -1)
}

func BenchmarkAwsPubSub(b *testing.B) {
	ctx := context.Background()
	sess, err := session.NewSession(&aws.Config{
		HTTPClient:  &http.Client{},
		Region:      aws.String(region),
		MaxRetries:  aws.Int(0),
	})
	if err != nil {
		b.Fatal(err)
	}
	snsClient := sns.New(sess, &aws.Config{})
	sqsClient := sqs.New(sess, &aws.Config{})
	topic := OpenTopic(ctx, snsClient, benchmarkTopicARN, nil)
	sub := OpenSubscription(ctx, sqsClient, benchmarkSubscriptionURL, nil)
	drivertest.RunBenchmarks(b, topic, sub)
}
<|endoftext|>


<commit_before>package push

import (
	"fmt"
	"io/ioutil"
	"os"
	"testing"
	"time"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const (
	CFEventuallyTimeout   = 300 * time.Second
	CFConsistentlyTimeout = 500 * time.Millisecond
	RealIsolationSegment  = "persistent_isolation_segment"
	PushCommandName       = "push"
	PublicDockerImage     = "cloudfoundry/diego-docker-app-custom"
)

var (
	// Suite Level
	organization string
	space        string
	realDir      string

	// Per Test Level
	homeDir string
)

func TestPush(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Push Integration Suite")
}

var _ = SynchronizedBeforeSuite(func() []byte {
	GinkgoWriter.Write([]byte("==============================Global FIRST Node Synchronized Before Each=============================="))
	SetDefaultEventuallyTimeout(CFEventuallyTimeout)
	SetDefaultConsistentlyDuration(CFConsistentlyTimeout)

	helpers.SetupSynchronizedSuite(func() {
		helpers.EnableFeatureFlag("diego_docker")
		helpers.EnableFeatureFlag("service_instance_sharing")
	})

	GinkgoWriter.Write([]byte("==============================End of Global FIRST Node Synchronized Before Each=============================="))

	return nil
}, func(_ []byte) {
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized Before Each==============================", GinkgoParallelNode())))
	// Ginkgo Globals
	SetDefaultEventuallyTimeout(CFEventuallyTimeout)
	SetDefaultConsistentlyDuration(CFConsistentlyTimeout)

	// Setup common environment variables
	helpers.TurnOffColors()

	homeDir = helpers.SetHomeDir()
	helpers.SetAPI()
	helpers.LoginCF()

	organization = helpers.NewOrgName()
	helpers.CreateOrg(organization)
	helpers.TargetOrg(organization)
	helpers.CreateSpace("empty-space")
	helpers.DestroyHomeDir(homeDir)

	var err error
	realDir, err = ioutil.TempDir("", "push-real-dir")
	Expect(err).ToNot(HaveOccurred())
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized Before Each==============================", GinkgoParallelNode())))
})

var _ = SynchronizedAfterSuite(func() {
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized After Each==============================", GinkgoParallelNode())))
	homeDir = helpers.SetHomeDir()
	helpers.SetAPI()
	helpers.LoginCF()
	helpers.QuickDeleteOrg(organization)
	Expect(os.RemoveAll(realDir)).ToNot(HaveOccurred())
	helpers.DestroyHomeDir(homeDir)
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized After Each==============================", GinkgoParallelNode())))
}, func() {})

var _ = BeforeEach(func() {
	GinkgoWriter.Write([]byte("==============================Global Before Each=============================="))
	homeDir = helpers.SetHomeDir()
	helpers.SetAPI()
	space = helpers.NewSpaceName()
	helpers.SetupCF(organization, space)
	GinkgoWriter.Write([]byte("==============================End of Global Before Each=============================="))
})

var _ = AfterEach(func() {
	GinkgoWriter.Write([]byte("==============================Global After Each=============================="))
	helpers.SetAPI()
	helpers.SetupCF(organization, space)
	helpers.QuickDeleteSpace(space)
	helpers.DestroyHomeDir(homeDir)
})
<commit_msg>Revert "test: redo removal of fakeservicebroker from push suite"<commit_after>package push

import (
	"fmt"
	"io/ioutil"
	"os"
	"testing"
	"time"

	"code.cloudfoundry.org/cli/integration/helpers/fakeservicebroker"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const (
	CFEventuallyTimeout   = 300 * time.Second
	CFConsistentlyTimeout = 500 * time.Millisecond
	RealIsolationSegment  = "persistent_isolation_segment"
	PushCommandName       = "push"
	PublicDockerImage     = "cloudfoundry/diego-docker-app-custom"
)

var (
	// Suite Level
	organization string
	space        string
	realDir      string

	// Per Test Level
	homeDir string
)

func TestPush(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Push Integration Suite")
}

var _ = SynchronizedBeforeSuite(func() []byte {
	GinkgoWriter.Write([]byte("==============================Global FIRST Node Synchronized Before Each=============================="))
	SetDefaultEventuallyTimeout(CFEventuallyTimeout)
	SetDefaultConsistentlyDuration(CFConsistentlyTimeout)

	helpers.SetupSynchronizedSuite(func() {
		helpers.EnableFeatureFlag("diego_docker")
		helpers.EnableFeatureFlag("service_instance_sharing")
	})

	fakeservicebroker.Setup()

	GinkgoWriter.Write([]byte("==============================End of Global FIRST Node Synchronized Before Each=============================="))

	return nil
}, func(_ []byte) {
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized Before Each==============================", GinkgoParallelNode())))
	// Ginkgo Globals
	SetDefaultEventuallyTimeout(CFEventuallyTimeout)
	SetDefaultConsistentlyDuration(CFConsistentlyTimeout)

	// Setup common environment variables
	helpers.TurnOffColors()

	homeDir = helpers.SetHomeDir()
	helpers.SetAPI()
	helpers.LoginCF()

	organization = helpers.NewOrgName()
	helpers.CreateOrg(organization)
	helpers.TargetOrg(organization)
	helpers.CreateSpace("empty-space")
	helpers.DestroyHomeDir(homeDir)

	var err error
	realDir, err = ioutil.TempDir("", "push-real-dir")
	Expect(err).ToNot(HaveOccurred())
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized Before Each==============================", GinkgoParallelNode())))
})

var _ = SynchronizedAfterSuite(func() {
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================Global Node %d Synchronized After Each==============================", GinkgoParallelNode())))
	fakeservicebroker.Cleanup()
	homeDir = helpers.SetHomeDir()
	helpers.SetAPI()
	helpers.LoginCF()
	helpers.QuickDeleteOrg(organization)
	Expect(os.RemoveAll(realDir)).ToNot(HaveOccurred())
	helpers.DestroyHomeDir(homeDir)
	GinkgoWriter.Write([]byte(fmt.Sprintf("==============================End of Global Node %d Synchronized After Each==============================", GinkgoParallelNode())))
}, func() {})

var _ = BeforeEach(func() {
	GinkgoWriter.Write([]byte("==============================Global Before Each=============================="))
	homeDir = helpers.SetHomeDir()
	helpers.SetAPI()
	space = helpers.NewSpaceName()
	helpers.SetupCF(organization, space)
	GinkgoWriter.Write([]byte("==============================End of Global Before Each=============================="))
})

var _ = AfterEach(func() {
	GinkgoWriter.Write([]byte("==============================Global After Each=============================="))
	helpers.SetAPI()
	helpers.SetupCF(organization, space)
	helpers.QuickDeleteSpace(space)
	helpers.DestroyHomeDir(homeDir)
})
<|endoftext|>


<commit_before>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Run the game of life in C using Go for parallelization.

package main

import (
	"flag"
	"fmt"
	"life"
)

const MAXDIM = 100

var dim = flag.Int("dim", 16, "board dimensions")
var gen = flag.Int("gen", 10, "generations")

func main() {
	flag.Parse()

	var a [MAXDIM * MAXDIM]int
	for i := 2; i < *dim; i += 8 {
		for j := 2; j < *dim-3; j += 8 {
			for y := 0; y < 3; y++ {
				a[i**dim+j+y] = 1
			}
		}
	}

	life.Run(*gen, *dim, *dim, &a)

	for i := 0; i < *dim; i++ {
		for j := 0; j < *dim; j++ {
			if a[i**dim+j] == 0 {
				fmt.Print(" ")
			} else {
				fmt.Print("X")
			}
		}
		fmt.Print("\n")
	}
}
<commit_msg>life: fix for new slice rules<commit_after>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Run the game of life in C using Go for parallelization.

package main

import (
	"flag"
	"fmt"
	"life"
)

const MAXDIM = 100

var dim = flag.Int("dim", 16, "board dimensions")
var gen = flag.Int("gen", 10, "generations")

func main() {
	flag.Parse()

	var a [MAXDIM * MAXDIM]int
	for i := 2; i < *dim; i += 8 {
		for j := 2; j < *dim-3; j += 8 {
			for y := 0; y < 3; y++ {
				a[i**dim+j+y] = 1
			}
		}
	}

	life.Run(*gen, *dim, *dim, a[:])

	for i := 0; i < *dim; i++ {
		for j := 0; j < *dim; j++ {
			if a[i**dim+j] == 0 {
				fmt.Print(" ")
			} else {
				fmt.Print("X")
			}
		}
		fmt.Print("\n")
	}
}
<|endoftext|>


<commit_before>package controllers

import (
	"github.com/robfig/revel"
	"os"
	fpath "path/filepath"
	"strings"
	"syscall"
)

type Static struct {
	*revel.Controller
}

// This method handles requests for files. The supplied prefix may be absolute
// or relative. If the prefix is relative it is assumed to be relative to the
// application directory. The filepath may either be just a file or an
// additional filepath to search for the given file. This response may return
// the following responses in the event of an error or invalid request;
//   403(Forbidden): If the prefix filepath combination results in a directory.
//   404(Not found): If the prefix and filepath combination results in a non-existent file.
//   500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel.
//
// Note that when defining routes in routes/conf the parameters must not have
// spaces around the comma.
//   Bad:  Static.Serve("public/img", "favicon.png")
//   Good: Static.Serve("public/img","favicon.png")
//
// Examples:
// Serving a directory
//   Route (conf/routes):
//     GET /public/{<.*>filepath} Static.Serve("public")
//   Request:
//     public/js/sessvars.js
//   Calls
//     Static.Serve("public","js/sessvars.js")
//
// Serving a file
//   Route (conf/routes):
//     GET /favicon.ico Static.Serve("public/img","favicon.png")
//   Request:
//     favicon.ico
//   Calls:
//     Static.Serve("public/img", "favicon.png")
func (c Static) Serve(prefix, filepath string) revel.Result {
	var basePath string

	if !fpath.IsAbs(prefix) {
		basePath = revel.BasePath
	}

	basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix))
	fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath))
	if !strings.HasPrefix(fname, basePathPrefix) {
		revel.WARN.Printf("Attempted to read file outside of base path: %s", fname)
		return c.NotFound("")
	}

	finfo, err := os.Stat(fname)
	if err != nil {
		if os.IsNotExist(err) || isNotDir(err) {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
		return c.RenderError(err)
	}

	if finfo.Mode().IsDir() {
		revel.WARN.Printf("Attempted directory listing of %s", fname)
		return c.Forbidden("Directory listing not allowed")
	}

	file, err := os.Open(fname)
	if err != nil {
		if os.IsNotExist(err) {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error opening '%s': %s", fname, err)
		return c.RenderError(err)
	}
	return c.RenderFile(file, revel.Inline)
}

// This method allows modules to serve binary files. The parameters are the same
// as Static.Serve with the additional module name pre-pended to the list of
// arguments.
func (c Static) ServeModule(moduleName, prefix, filepath string) revel.Result {
	var basePath string
	for _, module := range revel.Modules {
		if module.Name == moduleName {
			basePath = module.Path
		}
	}

	absPath := fpath.Join(basePath, fpath.FromSlash(prefix))

	return c.Serve(absPath, filepath)
}

func isNotDir(err error) bool {
	switch pe := err.(type) {
	case nil:
		return false
	case *os.PathError:
		err = pe.Err
	case *os.LinkError:
		err = pe.Err
	}
	return err == syscall.ENOTDIR
}
<commit_msg>a simpler patch<commit_after>package controllers

import (
	"github.com/robfig/revel"
	"os"
	fpath "path/filepath"
	"strings"
	"syscall"
)

type Static struct {
	*revel.Controller
}

// This method handles requests for files. The supplied prefix may be absolute
// or relative. If the prefix is relative it is assumed to be relative to the
// application directory. The filepath may either be just a file or an
// additional filepath to search for the given file. This response may return
// the following responses in the event of an error or invalid request;
//   403(Forbidden): If the prefix filepath combination results in a directory.
//   404(Not found): If the prefix and filepath combination results in a non-existent file.
//   500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel.
//
// Note that when defining routes in routes/conf the parameters must not have
// spaces around the comma.
//   Bad:  Static.Serve("public/img", "favicon.png")
//   Good: Static.Serve("public/img","favicon.png")
//
// Examples:
// Serving a directory
//   Route (conf/routes):
//     GET /public/{<.*>filepath} Static.Serve("public")
//   Request:
//     public/js/sessvars.js
//   Calls
//     Static.Serve("public","js/sessvars.js")
//
// Serving a file
//   Route (conf/routes):
//     GET /favicon.ico Static.Serve("public/img","favicon.png")
//   Request:
//     favicon.ico
//   Calls:
//     Static.Serve("public/img", "favicon.png")
func (c Static) Serve(prefix, filepath string) revel.Result {
	var basePath string

	if !fpath.IsAbs(prefix) {
		basePath = revel.BasePath
	}

	basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix))
	fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath))
	if !strings.HasPrefix(fname, basePathPrefix) {
		revel.WARN.Printf("Attempted to read file outside of base path: %s", fname)
		return c.NotFound("")
	}

	finfo, err := os.Stat(fname)
	if err != nil {
		if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
		return c.RenderError(err)
	}

	if finfo.Mode().IsDir() {
		revel.WARN.Printf("Attempted directory listing of %s", fname)
		return c.Forbidden("Directory listing not allowed")
	}

	file, err := os.Open(fname)
	if err != nil {
		if os.IsNotExist(err) {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error opening '%s': %s", fname, err)
		return c.RenderError(err)
	}
	return c.RenderFile(file, revel.Inline)
}

// This method allows modules to serve binary files. The parameters are the same
// as Static.Serve with the additional module name pre-pended to the list of
// arguments.
func (c Static) ServeModule(moduleName, prefix, filepath string) revel.Result {
	var basePath string
	for _, module := range revel.Modules {
		if module.Name == moduleName {
			basePath = module.Path
		}
	}

	absPath := fpath.Join(basePath, fpath.FromSlash(prefix))

	return c.Serve(absPath, filepath)
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	rss "github.com/jteeuwen/go-pkg-rss"
	"github.com/jteeuwen/go-pkg-xmlx"
	_ "github.com/mattn/go-sqlite3"
	"github.com/nicksnyder/go-i18n/i18n"
	"net/http"
	"os"
	"time"
)

// The path to the configuration file to use
const CONFIG_FILE string = "./config/config.json"

// The name of the file to store our SQLite database in
const DB_FILENAME string = "feeds.db"

// A global configuration instance. Must be instantiated properly in main().
var Configuration Config

// A global database connection. Must be instantiated properly in main().
var DBConnection *sql.DB

// Map expected charsets provided by a client to the function that handles
// incoming items/channels from a feed, checking that it matches the expected charset
// And/or doing any extra handling
var CharsetReaders map[string]xmlx.CharsetFunc = map[string]xmlx.CharsetFunc{
	"iso-8859-1": HandleISO88591,
}

/**
 * Describes a feed, so that, when items of the feed are handled,
 * the appropriate functionality can be invoked.
 */
type Feed struct {
	Id      int
	Url     string `json:"url"`
	Type    string `json:"type"`
	Charset string `json:"charset"`
}

/**
 * Handle the receipt of a new channel.
 * @param {*rss.Feed} feed - A pointer to the object representing the feed received from
 * @param {[]*rss.Channel] newChannels - An array of pointers to received channels
 */
func channelFeedHandler(feed *rss.Feed, newChannels []*rss.Channel) {
	return
}

/**
 * Handle the receipt of a new item.
 * @param {*rss.Feed} feed - A pointer to the object representing the feed received from
 * @param {*rss.Channel} channel - A pointer to the channel object the item was received from
 * @param {[]*rss.Item} newItems - An array of pointers to items received from the channel
 */
func itemFeedHandler(feed *rss.Feed, channel *rss.Channel, newItems []*rss.Item) {
	T, _ := i18n.Tfunc(os.Getenv("CENOLANG"), "en-us")
	for _, item := range newItems {
		saveErr := SaveNewItem(DBConnection, feed.Url, item)
		if saveErr != nil {
			fmt.Println(T("db_store_error_rdr", map[string]interface{}{"Error": saveErr.Error()}))
		}
	}
}

/**
 * Periodically polls an RSS or Atom feed for new items.
 * @param {string} URL - The address of the feed
 * @param {xmlx.CharsetFunc} charsetReader - A function for handling the charset of items
 */
func pollFeed(URL string, charsetReader xmlx.CharsetFunc) {
	// Poll every five seconds
	feed := rss.New(5, true, channelFeedHandler, itemFeedHandler)
	for {
		if err := feed.Fetch(URL, charsetReader); err != nil {
			// Handle error condition
		}
		<-time.After(time.Duration(feed.SecondsTillUpdate() * 1e9))
	}
}

/**
 * Handle the following of a feed in a separate goroutine.
 * @param {chan Feed} requests - A channel through which descriptions of feeds to be followed are received
 */
func followFeeds(requests chan Feed) {
	for {
		request := <-requests
		fmt.Println("Got a request to handle a feed.")
		fmt.Println(request)
		if request.Charset == "" {
			go pollFeed(request.Url, nil)
		} else {
			charsetFn, found := CharsetReaders[request.Charset]
			if found {
				go pollFeed(request.Url, charsetFn)
			} else {
				go pollFeed(request.Url, nil)
			}
		}
	}
}

/**
 * Handle requests to have a new RSS or Atom feed followed.
 * @param {chan Feed} requests - A channel through which descriptions of feeds to be followed are received
 */
func followHandler(requests chan Feed) func(http.ResponseWriter, *http.Request) {
	T, _ := i18n.Tfunc(os.Getenv("CENOLANG"), "en-us")
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("Got request")
		if r.Method != "POST" {
			w.Write([]byte(T("method_not_impl_rdr")))
			return
		}
		feedInfo := Feed{}
		decoder := json.NewDecoder(r.Body)
		if err := decoder.Decode(&feedInfo); err != nil {
			fmt.Println("Error decoding JSON")
			fmt.Println(err)
			w.Write([]byte(T("minvalid_follow_req_rdr")))
			return
		}
		fmt.Println("JSON decoded")
		foundFeed, lookupErr := GetFeedByUrl(DBConnection, feedInfo.Url)
		if lookupErr != nil {
			w.Write([]byte(T("db_lookup_error_rdr", map[string]interface{}{"Error": lookupErr.Error()})))
		} else if foundFeed.Id != -1 {
			w.Write([]byte(T("feed_exists_rdr", map[string]interface{}{"URL": feedInfo.Url})))
		} else {
			fmt.Println("Feed doesn't exist")
			saveErr := SaveNewFeed(DBConnection, feedInfo)
			if saveErr != nil {
				w.Write([]byte(T("db_store_error_rdr", map[string]interface{}{"Error": saveErr.Error()})))
			} else {
				requests <- feedInfo
				w.Write([]byte(T("req_handle_success_rdr")))
			}
		}
	}
}

func main() {
	// Configure the i18n library to use the preferred language set in the CENOLANG environment variable
	setLanguage := os.Getenv("CENOLANG")
	if setLanguage == "" {
		os.Setenv("CENOLANG", "en-us")
		setLanguage = "en-us"
	}
	i18n.MustLoadTranslationFile("./translations/" + setLanguage + ".all.json")
	T, _ := i18n.Tfunc(setLanguage, "en-us")
	// Check that the configuration supplied has valid fields, or panic
	conf, err := ReadConfigFile(CONFIG_FILE)
	if err != nil {
		panic(T("no_config_rdr", map[string]interface{}{"Location": CONFIG_FILE}))
	} else if !ValidConfiguration(conf) {
		panic(T("invalid_config_rdr"))
	} else {
		Configuration = conf
	}
	// Establish a connection to the database
	var dbErr error
	DBConnection, dbErr = InitDBConnection(DB_FILENAME)
	if dbErr != nil {
		panic(T("database_init_error_rdr", map[string]interface{}{"Error": dbErr.Error()}))
	}
	// Set up the HTTP server to listen for requests for new feeds to read
	requestNewFollow := make(chan Feed)
	go followFeeds(requestNewFollow)
	http.HandleFunc("/follow", followHandler(requestNewFollow))
	fmt.Println(T("listening_msg_rdr", map[string]interface{}{"Port": Configuration.PortNumber}))
	if err := http.ListenAndServe(Configuration.PortNumber, nil); err != nil {
		panic(err)
	}
}
<commit_msg>Have the reader server serve assets like a normal http server for testing purposes<commit_after>package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	rss "github.com/jteeuwen/go-pkg-rss"
	"github.com/jteeuwen/go-pkg-xmlx"
	_ "github.com/mattn/go-sqlite3"
	"github.com/nicksnyder/go-i18n/i18n"
	"net/http"
	"os"
	"time"
)

// The path to the configuration file to use
const CONFIG_FILE string = "./config/config.json"

// The name of the file to store our SQLite database in
const DB_FILENAME string = "feeds.db"

// A global configuration instance. Must be instantiated properly in main().
var Configuration Config

// A global database connection. Must be instantiated properly in main().
var DBConnection *sql.DB

// Map expected charsets provided by a client to the function that handles
// incoming items/channels from a feed, checking that it matches the expected charset
// And/or doing any extra handling
var CharsetReaders map[string]xmlx.CharsetFunc = map[string]xmlx.CharsetFunc{
	"iso-8859-1": HandleISO88591,
}

/**
 * Describes a feed, so that, when items of the feed are handled,
 * the appropriate functionality can be invoked.
 */
type Feed struct {
	Id      int
	Url     string `json:"url"`
	Type    string `json:"type"`
	Charset string `json:"charset"`
}

/**
 * Handle the receipt of a new channel.
 * @param {*rss.Feed} feed - A pointer to the object representing the feed received from
 * @param {[]*rss.Channel] newChannels - An array of pointers to received channels
 */
func channelFeedHandler(feed *rss.Feed, newChannels []*rss.Channel) {
	return
}

/**
 * Handle the receipt of a new item.
 * @param {*rss.Feed} feed - A pointer to the object representing the feed received from
 * @param {*rss.Channel} channel - A pointer to the channel object the item was received from
 * @param {[]*rss.Item} newItems - An array of pointers to items received from the channel
 */
func itemFeedHandler(feed *rss.Feed, channel *rss.Channel, newItems []*rss.Item) {
	T, _ := i18n.Tfunc(os.Getenv("CENOLANG"), "en-us")
	for _, item := range newItems {
		saveErr := SaveNewItem(DBConnection, feed.Url, item)
		if saveErr != nil {
			fmt.Println(T("db_store_error_rdr", map[string]interface{}{"Error": saveErr.Error()}))
		}
	}
}

/**
 * Periodically polls an RSS or Atom feed for new items.
 * @param {string} URL - The address of the feed
 * @param {xmlx.CharsetFunc} charsetReader - A function for handling the charset of items
 */
func pollFeed(URL string, charsetReader xmlx.CharsetFunc) {
	// Poll every five seconds
	feed := rss.New(5, true, channelFeedHandler, itemFeedHandler)
	for {
		if err := feed.Fetch(URL, charsetReader); err != nil {
			// Handle error condition
		}
		<-time.After(time.Duration(feed.SecondsTillUpdate() * 1e9))
	}
}

/**
 * Handle the following of a feed in a separate goroutine.
 * @param {chan Feed} requests - A channel through which descriptions of feeds to be followed are received
 */
func followFeeds(requests chan Feed) {
	for {
		request := <-requests
		fmt.Println("Got a request to handle a feed.")
		fmt.Println(request)
		if request.Charset == "" {
			go pollFeed(request.Url, nil)
		} else {
			charsetFn, found := CharsetReaders[request.Charset]
			if found {
				go pollFeed(request.Url, charsetFn)
			} else {
				go pollFeed(request.Url, nil)
			}
		}
	}
}

/**
 * Handle requests to have a new RSS or Atom feed followed.
 * @param {chan Feed} requests - A channel through which descriptions of feeds to be followed are received
 */
func followHandler(requests chan Feed) func(http.ResponseWriter, *http.Request) {
	T, _ := i18n.Tfunc(os.Getenv("CENOLANG"), "en-us")
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("Got request")
		if r.Method != "POST" {
			w.Write([]byte(T("method_not_impl_rdr")))
			return
		}
		feedInfo := Feed{}
		decoder := json.NewDecoder(r.Body)
		if err := decoder.Decode(&feedInfo); err != nil {
			fmt.Println("Error decoding JSON")
			fmt.Println(err)
			w.Write([]byte(T("minvalid_follow_req_rdr")))
			return
		}
		fmt.Println("JSON decoded")
		foundFeed, lookupErr := GetFeedByUrl(DBConnection, feedInfo.Url)
		if lookupErr != nil {
			w.Write([]byte(T("db_lookup_error_rdr", map[string]interface{}{"Error": lookupErr.Error()})))
		} else if foundFeed.Id != -1 {
			w.Write([]byte(T("feed_exists_rdr", map[string]interface{}{"URL": feedInfo.Url})))
		} else {
			fmt.Println("Feed doesn't exist")
			saveErr := SaveNewFeed(DBConnection, feedInfo)
			if saveErr != nil {
				w.Write([]byte(T("db_store_error_rdr", map[string]interface{}{"Error": saveErr.Error()})))
			} else {
				requests <- feedInfo
				w.Write([]byte(T("req_handle_success_rdr")))
			}
		}
	}
}

/**
 * Build the portal page with information about articles already inserted into Freenet
 */
func createPortalPage(w http.ResponseWriter, r *http.Request) {
	//T, _ := i18n.Tfunc(os.Getenv("CENOLANG"), "en-us")
	// TODO - Populate the template
	w.Write([]byte("Welcome to the CENO Portal"))
}

func main() {
	// Configure the i18n library to use the preferred language set in the CENOLANG environment variable
	setLanguage := os.Getenv("CENOLANG")
	if setLanguage == "" {
		os.Setenv("CENOLANG", "en-us")
		setLanguage = "en-us"
	}
	i18n.MustLoadTranslationFile("./translations/" + setLanguage + ".all.json")
	T, _ := i18n.Tfunc(setLanguage, "en-us")
	// Check that the configuration supplied has valid fields, or panic
	conf, err := ReadConfigFile(CONFIG_FILE)
	if err != nil {
		panic(T("no_config_rdr", map[string]interface{}{"Location": CONFIG_FILE}))
	} else if !ValidConfiguration(conf) {
		panic(T("invalid_config_rdr"))
	} else {
		Configuration = conf
	}
	// Establish a connection to the database
	var dbErr error
	DBConnection, dbErr = InitDBConnection(DB_FILENAME)
	if dbErr != nil {
		panic(T("database_init_error_rdr", map[string]interface{}{"Error": dbErr.Error()}))
	}
	// Set up the HTTP server to listen for requests for new feeds to read
	requestNewFollow := make(chan Feed)
	go followFeeds(requestNewFollow)
	http.Handle("/", http.FileServer(http.Dir("./static")))
	http.HandleFunc("/follow", followHandler(requestNewFollow))
	http.HandleFunc("/portal", createPortalPage)
	fmt.Println(T("listening_msg_rdr", map[string]interface{}{"Port": Configuration.PortNumber}))
	if err := http.ListenAndServe(Configuration.PortNumber, nil); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package api_test

import (
	. "github.com/cloudfoundry/cli/cf/api"
	"github.com/cloudfoundry/cli/cf/configuration"
	"github.com/cloudfoundry/cli/cf/errors"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/net"
	testapi "github.com/cloudfoundry/cli/testhelpers/api"
	testconfig "github.com/cloudfoundry/cli/testhelpers/configuration"
	testnet "github.com/cloudfoundry/cli/testhelpers/net"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"net/http"
	"net/http/httptest"
	"time"
)

var _ = Describe("StacksRepo", func() {
	var (
		testServer  *httptest.Server
		testHandler *testnet.TestHandler
		configRepo  configuration.ReadWriter
		repo        StackRepository
	)

	setupTestServer := func(reqs ...testnet.TestRequest) {
		testServer, testHandler = testnet.NewServer(reqs)
		configRepo.SetApiEndpoint(testServer.URL)
	}

	BeforeEach(func() {
		configRepo = testconfig.NewRepositoryWithDefaults()
		configRepo.SetAccessToken("BEARER my_access_token")

		gateway := net.NewCloudControllerGateway((configRepo), time.Now)
		repo = NewCloudControllerStackRepository(configRepo, gateway)
	})

	AfterEach(func() {
		testServer.Close()
	})

	Describe("FindByName", func() {
		Context("when a stack exists", func() {
			BeforeEach(func() {
				setupTestServer(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks?q=name%3Alinux",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body: `
				{
					"resources": [
						{
						  "metadata": { "guid": "custom-linux-guid" },
						  "entity": { "name": "custom-linux" }
						}
					]
				}`}})
			})

			It("finds the stack", func() {
				stack, err := repo.FindByName("linux")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(err).NotTo(HaveOccurred())
				Expect(stack).To(Equal(models.Stack{
					Name: "custom-linux",
					Guid: "custom-linux-guid",
				}))
			})
		})

		Context("when a stack does not exist", func() {
			BeforeEach(func() {
				setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks?q=name%3Alinux",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body:   ` { "resources": []}`,
					}}))
			})

			It("returns an error", func() {
				_, err := repo.FindByName("linux")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{}))
			})
		})
	})

	Describe("FindAll", func() {
		BeforeEach(func() {
			setupTestServer(
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body: `{
							"next_url": "/v2/stacks?page=2",
							"resources": [
								{
									"metadata": {
										"guid": "stack-guid-1",
										"url": "/v2/stacks/stack-guid-1",
										"created_at": "2013-08-31 01:32:40 +0000",
										"updated_at": "2013-08-31 01:32:40 +0000"
									},
									"entity": {
										"name": "lucid64",
										"description": "Ubuntu 10.04"
									}
								}
							]
						}`}}),

				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body: `
						{
							"resources": [
								{
									"metadata": {
										"guid": "stack-guid-2",
										"url": "/v2/stacks/stack-guid-2",
										"created_at": "2013-08-31 01:32:40 +0000",
										"updated_at": "2013-08-31 01:32:40 +0000"
									},
									"entity": {
										"name": "lucid64custom",
										"description": "Fake Ubuntu 10.04"
									}
								}
							]
						}`}}))
		})

		It("finds all the stacks", func() {
			stacks, err := repo.FindAll()

			Expect(testHandler).To(testnet.HaveAllRequestsCalled())
			Expect(err).NotTo(HaveOccurred())
			Expect(stacks).To(Equal([]models.Stack{
				{
					Guid:        "stack-guid-1",
					Name:        "lucid64",
					Description: "Ubuntu 10.04",
				},
				{
					Guid:        "stack-guid-2",
					Name:        "lucid64custom",
					Description: "Fake Ubuntu 10.04",
				},
			}))
		})
	})

})
<commit_msg>Cleanup imports for stacks repository tests<commit_after>package api_test

import (
	"net/http"
	"net/http/httptest"
	"time"

	"github.com/cloudfoundry/cli/cf/configuration"
	"github.com/cloudfoundry/cli/cf/errors"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/net"
	testapi "github.com/cloudfoundry/cli/testhelpers/api"
	testconfig "github.com/cloudfoundry/cli/testhelpers/configuration"
	testnet "github.com/cloudfoundry/cli/testhelpers/net"

	. "github.com/cloudfoundry/cli/cf/api"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("StacksRepo", func() {
	var (
		testServer  *httptest.Server
		testHandler *testnet.TestHandler
		configRepo  configuration.ReadWriter
		repo        StackRepository
	)

	setupTestServer := func(reqs ...testnet.TestRequest) {
		testServer, testHandler = testnet.NewServer(reqs)
		configRepo.SetApiEndpoint(testServer.URL)
	}

	BeforeEach(func() {
		configRepo = testconfig.NewRepositoryWithDefaults()
		configRepo.SetAccessToken("BEARER my_access_token")

		gateway := net.NewCloudControllerGateway((configRepo), time.Now)
		repo = NewCloudControllerStackRepository(configRepo, gateway)
	})

	AfterEach(func() {
		testServer.Close()
	})

	Describe("FindByName", func() {
		Context("when a stack exists", func() {
			BeforeEach(func() {
				setupTestServer(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks?q=name%3Alinux",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body: `
				{
					"resources": [
						{
						  "metadata": { "guid": "custom-linux-guid" },
						  "entity": { "name": "custom-linux" }
						}
					]
				}`}})
			})

			It("finds the stack", func() {
				stack, err := repo.FindByName("linux")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(err).NotTo(HaveOccurred())
				Expect(stack).To(Equal(models.Stack{
					Name: "custom-linux",
					Guid: "custom-linux-guid",
				}))
			})
		})

		Context("when a stack does not exist", func() {
			BeforeEach(func() {
				setupTestServer(testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks?q=name%3Alinux",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body:   ` { "resources": []}`,
					}}))
			})

			It("returns an error", func() {
				_, err := repo.FindByName("linux")

				Expect(testHandler).To(testnet.HaveAllRequestsCalled())
				Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{}))
			})
		})
	})

	Describe("FindAll", func() {
		BeforeEach(func() {
			setupTestServer(
				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body: `{
							"next_url": "/v2/stacks?page=2",
							"resources": [
								{
									"metadata": {
										"guid": "stack-guid-1",
										"url": "/v2/stacks/stack-guid-1",
										"created_at": "2013-08-31 01:32:40 +0000",
										"updated_at": "2013-08-31 01:32:40 +0000"
									},
									"entity": {
										"name": "lucid64",
										"description": "Ubuntu 10.04"
									}
								}
							]
						}`}}),

				testapi.NewCloudControllerTestRequest(testnet.TestRequest{
					Method: "GET",
					Path:   "/v2/stacks",
					Response: testnet.TestResponse{
						Status: http.StatusOK,
						Body: `
						{
							"resources": [
								{
									"metadata": {
										"guid": "stack-guid-2",
										"url": "/v2/stacks/stack-guid-2",
										"created_at": "2013-08-31 01:32:40 +0000",
										"updated_at": "2013-08-31 01:32:40 +0000"
									},
									"entity": {
										"name": "lucid64custom",
										"description": "Fake Ubuntu 10.04"
									}
								}
							]
						}`}}))
		})

		It("finds all the stacks", func() {
			stacks, err := repo.FindAll()

			Expect(testHandler).To(testnet.HaveAllRequestsCalled())
			Expect(err).NotTo(HaveOccurred())
			Expect(stacks).To(Equal([]models.Stack{
				{
					Guid:        "stack-guid-1",
					Name:        "lucid64",
					Description: "Ubuntu 10.04",
				},
				{
					Guid:        "stack-guid-2",
					Name:        "lucid64custom",
					Description: "Fake Ubuntu 10.04",
				},
			}))
		})
	})
})
<|endoftext|>


<commit_before>package harvest

import (
	"crypto/md5"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
)

func mockResponse(paths ...string) *httptest.Server {
	parts := []string{".", "testdata"}
	filename := filepath.Join(append(parts, paths...)...)

	mockData, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.Write(mockData)
	}))
}

func mockDynamicPathResponse() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {

		// Build the path for the dynamic content
		parts := []string{".", "testdata"}
		parts = append(parts, strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")...)
		// Remove security strings
		queryStringPart := r.URL.RawQuery
		if queryStringPart != "" {
			parts[len(parts)-1] = fmt.Sprintf("%s-%x", parts[len(parts)-1], md5.Sum([]byte(queryStringPart)))
		}
		parts[len(parts)-1] = parts[len(parts)-1] + ".json"
		filename := filepath.Join(parts...)

		fmt.Println(filename)

		if _, err := os.Stat(filename); os.IsNotExist(err) {
			http.Error(rw, fmt.Sprintf("%s doesn't exist. Create it with the mock you'd like to use.\n Args were: %s", filename, queryStringPart), http.StatusNotFound)
			return
		}

		mockData, err := ioutil.ReadFile(filename)
		if err != nil {
			log.Fatal(err)
		}
		rw.Write(mockData)

	}))
}

func mockErrorResponse(code int) *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		http.Error(rw, "An error occurred", code)
	}))
}
<commit_msg>Removes noisy output.<commit_after>package harvest

import (
	"crypto/md5"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
)

func mockResponse(paths ...string) *httptest.Server {
	parts := []string{".", "testdata"}
	filename := filepath.Join(append(parts, paths...)...)

	mockData, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		rw.Write(mockData)
	}))
}

func mockDynamicPathResponse() *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {

		// Build the path for the dynamic content
		parts := []string{".", "testdata"}
		parts = append(parts, strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")...)
		// Remove security strings
		queryStringPart := r.URL.RawQuery
		if queryStringPart != "" {
			parts[len(parts)-1] = fmt.Sprintf("%s-%x", parts[len(parts)-1], md5.Sum([]byte(queryStringPart)))
		}
		parts[len(parts)-1] = parts[len(parts)-1] + ".json"
		filename := filepath.Join(parts...)

		if _, err := os.Stat(filename); os.IsNotExist(err) {
			http.Error(rw, fmt.Sprintf("%s doesn't exist. Create it with the mock you'd like to use.\n Args were: %s", filename, queryStringPart), http.StatusNotFound)
			return
		}

		mockData, err := ioutil.ReadFile(filename)
		if err != nil {
			log.Fatal(err)
		}
		rw.Write(mockData)

	}))
}

func mockErrorResponse(code int) *httptest.Server {
	return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		http.Error(rw, "An error occurred", code)
	}))
}
<|endoftext|>


<commit_before>// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
	"fmt"
	"html/template"
	"strconv"
	"strings"

	"github.com/go-xorm/xorm"

	api "github.com/gogits/go-gogs-client"

	"github.com/gogits/gogs/modules/base"
)

// Label represents a label of repository for issues.
type Label struct {
	ID              int64 `xorm:"pk autoincr"`
	RepoID          int64 `xorm:"INDEX"`
	Name            string
	Color           string `xorm:"VARCHAR(7)"`
	NumIssues       int
	NumClosedIssues int
	NumOpenIssues   int  `xorm:"-"`
	IsChecked       bool `xorm:"-"`
}

func (label *Label) APIFormat() *api.Label {
	return &api.Label{
		ID:    label.ID,
		Name:  label.Name,
		Color: label.Color,
	}
}

// CalOpenIssues calculates the open issues of label.
func (label *Label) CalOpenIssues() {
	label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
}

// ForegroundColor calculates the text color for labels based
// on their background color.
func (l *Label) ForegroundColor() template.CSS {
	if strings.HasPrefix(l.Color, "#") {
		if color, err := strconv.ParseUint(l.Color[1:], 16, 64); err == nil {
			r := float32(0xFF & (color >> 16))
			g := float32(0xFF & (color >> 8))
			b := float32(0xFF & color)
			luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255

			if luminance < 0.5 {
				return template.CSS("#fff")
			}
		}
	}

	// default to black
	return template.CSS("#000")
}

// NewLabel creates new label of repository.
func NewLabel(l *Label) error {
	_, err := x.Insert(l)
	return err
}

// getLabelInRepoByID returns a label by ID in given repository.
// If pass repoID as 0, then ORM will ignore limitation of repository
// and can return arbitrary label with any valid ID.
func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
	if labelID <= 0 {
		return nil, ErrLabelNotExist{labelID, repoID}
	}

	l := &Label{
		ID:     labelID,
		RepoID: repoID,
	}
	has, err := x.Get(l)
	if err != nil {
		return nil, err
	} else if !has {
		return nil, ErrLabelNotExist{l.ID, l.RepoID}
	}
	return l, nil
}

// GetLabelByID returns a label by given ID.
func GetLabelByID(id int64) (*Label, error) {
	return getLabelInRepoByID(x, 0, id)
}

// GetLabelInRepoByID returns a label by ID in given repository.
func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) {
	return getLabelInRepoByID(x, repoID, labelID)
}

// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,
// it silently ignores label IDs that are not belong to the repository.
func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
	labels := make([]*Label, 0, len(labelIDs))
	return labels, x.Where("repo_id = ?", repoID).In("id", base.Int64sToStrings(labelIDs)).Find(&labels)
}

// GetLabelsByRepoID returns all labels that belong to given repository by ID.
func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
	labels := make([]*Label, 0, 10)
	return labels, x.Where("repo_id = ?", repoID).Find(&labels)
}

func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
	issueLabels, err := getIssueLabels(e, issueID)
	if err != nil {
		return nil, fmt.Errorf("getIssueLabels: %v", err)
	} else if len(issueLabels) == 0 {
		return []*Label{}, nil
	}

	labelIDs := make([]int64, len(issueLabels))
	for i := range issueLabels {
		labelIDs[i] = issueLabels[i].LabelID
	}

	labels := make([]*Label, 0, len(labelIDs))
	return labels, e.Where("id > 0").In("id", base.Int64sToStrings(labelIDs)).Find(&labels)
}

// GetLabelsByIssueID returns all labels that belong to given issue by ID.
func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
	return getLabelsByIssueID(x, issueID)
}

func updateLabel(e Engine, l *Label) error {
	_, err := e.Id(l.ID).AllCols().Update(l)
	return err
}

// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
	return updateLabel(x, l)
}

// DeleteLabel delete a label of given repository.
func DeleteLabel(repoID, labelID int64) error {
	_, err := GetLabelInRepoByID(repoID, labelID)
	if err != nil {
		if IsErrLabelNotExist(err) {
			return nil
		}
		return err
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if _, err = sess.Id(labelID).Delete(new(Label)); err != nil {
		return err
	} else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil {
		return err
	}

	return sess.Commit()
}

// .___                            .____          ___.          .__
// |   | ______ ________ __   ____ |    |   _____ \_ |__   ____ |  |
// |   |/  ___//  ___/  |  \_/ __ \|    |   \__  \ | __ \_/ __ \|  |
// |   |\___ \ \___ \|  |  /\  ___/|    |___ / __ \| \_\ \  ___/|  |__
// |___/____  >____  >____/  \___  >_______ (____  /___  /\___  >____/
//          \/     \/            \/        \/    \/    \/     \/

// IssueLabel represetns an issue-lable relation.
type IssueLabel struct {
	ID      int64 `xorm:"pk autoincr"`
	IssueID int64 `xorm:"UNIQUE(s)"`
	LabelID int64 `xorm:"UNIQUE(s)"`
}

func hasIssueLabel(e Engine, issueID, labelID int64) bool {
	has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
	return has
}

// HasIssueLabel returns true if issue has been labeled.
func HasIssueLabel(issueID, labelID int64) bool {
	return hasIssueLabel(x, issueID, labelID)
}

func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
	if _, err = e.Insert(&IssueLabel{
		IssueID: issue.ID,
		LabelID: label.ID,
	}); err != nil {
		return err
	}

	label.NumIssues++
	if issue.IsClosed {
		label.NumClosedIssues++
	}
	return updateLabel(e, label)
}

// NewIssueLabel creates a new issue-label relation.
func NewIssueLabel(issue *Issue, label *Label) (err error) {
	if HasIssueLabel(issue.ID, label.ID) {
		return nil
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = newIssueLabel(sess, issue, label); err != nil {
		return err
	}

	return sess.Commit()
}

func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) {
	for i := range labels {
		if hasIssueLabel(e, issue.ID, labels[i].ID) {
			continue
		}

		if err = newIssueLabel(e, issue, labels[i]); err != nil {
			return fmt.Errorf("newIssueLabel: %v", err)
		}
	}

	return nil
}

// NewIssueLabels creates a list of issue-label relations.
func NewIssueLabels(issue *Issue, labels []*Label) (err error) {
	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = newIssueLabels(sess, issue, labels); err != nil {
		return err
	}

	return sess.Commit()
}

func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
	issueLabels := make([]*IssueLabel, 0, 10)
	return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
}

// GetIssueLabels returns all issue-label relations of given issue by ID.
func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
	return getIssueLabels(x, issueID)
}

func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
	if _, err = e.Delete(&IssueLabel{
		IssueID: issue.ID,
		LabelID: label.ID,
	}); err != nil {
		return err
	}

	label.NumIssues--
	if issue.IsClosed {
		label.NumClosedIssues--
	}
	return updateLabel(e, label)
}

// DeleteIssueLabel deletes issue-label relation.
func DeleteIssueLabel(issue *Issue, label *Label) (err error) {
	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = deleteIssueLabel(sess, issue, label); err != nil {
		return err
	}

	return sess.Commit()
}
<commit_msg>Fix #3189: Sort labels by name (#3446)<commit_after>// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
	"fmt"
	"html/template"
	"strconv"
	"strings"

	"github.com/go-xorm/xorm"

	api "github.com/gogits/go-gogs-client"

	"github.com/gogits/gogs/modules/base"
)

// Label represents a label of repository for issues.
type Label struct {
	ID              int64 `xorm:"pk autoincr"`
	RepoID          int64 `xorm:"INDEX"`
	Name            string
	Color           string `xorm:"VARCHAR(7)"`
	NumIssues       int
	NumClosedIssues int
	NumOpenIssues   int  `xorm:"-"`
	IsChecked       bool `xorm:"-"`
}

func (label *Label) APIFormat() *api.Label {
	return &api.Label{
		ID:    label.ID,
		Name:  label.Name,
		Color: label.Color,
	}
}

// CalOpenIssues calculates the open issues of label.
func (label *Label) CalOpenIssues() {
	label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
}

// ForegroundColor calculates the text color for labels based
// on their background color.
func (l *Label) ForegroundColor() template.CSS {
	if strings.HasPrefix(l.Color, "#") {
		if color, err := strconv.ParseUint(l.Color[1:], 16, 64); err == nil {
			r := float32(0xFF & (color >> 16))
			g := float32(0xFF & (color >> 8))
			b := float32(0xFF & color)
			luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255

			if luminance < 0.5 {
				return template.CSS("#fff")
			}
		}
	}

	// default to black
	return template.CSS("#000")
}

// NewLabel creates new label of repository.
func NewLabel(l *Label) error {
	_, err := x.Insert(l)
	return err
}

// getLabelInRepoByID returns a label by ID in given repository.
// If pass repoID as 0, then ORM will ignore limitation of repository
// and can return arbitrary label with any valid ID.
func getLabelInRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
	if labelID <= 0 {
		return nil, ErrLabelNotExist{labelID, repoID}
	}

	l := &Label{
		ID:     labelID,
		RepoID: repoID,
	}
	has, err := x.Get(l)
	if err != nil {
		return nil, err
	} else if !has {
		return nil, ErrLabelNotExist{l.ID, l.RepoID}
	}
	return l, nil
}

// GetLabelByID returns a label by given ID.
func GetLabelByID(id int64) (*Label, error) {
	return getLabelInRepoByID(x, 0, id)
}

// GetLabelInRepoByID returns a label by ID in given repository.
func GetLabelInRepoByID(repoID, labelID int64) (*Label, error) {
	return getLabelInRepoByID(x, repoID, labelID)
}

// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,
// it silently ignores label IDs that are not belong to the repository.
func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
	labels := make([]*Label, 0, len(labelIDs))
	return labels, x.Where("repo_id = ?", repoID).In("id", base.Int64sToStrings(labelIDs)).Asc("name").Find(&labels)
}

// GetLabelsByRepoID returns all labels that belong to given repository by ID.
func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
	labels := make([]*Label, 0, 10)
	return labels, x.Where("repo_id = ?", repoID).Asc("name").Find(&labels)
}

func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
	issueLabels, err := getIssueLabels(e, issueID)
	if err != nil {
		return nil, fmt.Errorf("getIssueLabels: %v", err)
	} else if len(issueLabels) == 0 {
		return []*Label{}, nil
	}

	labelIDs := make([]int64, len(issueLabels))
	for i := range issueLabels {
		labelIDs[i] = issueLabels[i].LabelID
	}

	labels := make([]*Label, 0, len(labelIDs))
	return labels, e.Where("id > 0").In("id", base.Int64sToStrings(labelIDs)).Asc("name").Find(&labels)
}

// GetLabelsByIssueID returns all labels that belong to given issue by ID.
func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
	return getLabelsByIssueID(x, issueID)
}

func updateLabel(e Engine, l *Label) error {
	_, err := e.Id(l.ID).AllCols().Update(l)
	return err
}

// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
	return updateLabel(x, l)
}

// DeleteLabel delete a label of given repository.
func DeleteLabel(repoID, labelID int64) error {
	_, err := GetLabelInRepoByID(repoID, labelID)
	if err != nil {
		if IsErrLabelNotExist(err) {
			return nil
		}
		return err
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if _, err = sess.Id(labelID).Delete(new(Label)); err != nil {
		return err
	} else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil {
		return err
	}

	return sess.Commit()
}

// .___                            .____          ___.          .__
// |   | ______ ________ __   ____ |    |   _____ \_ |__   ____ |  |
// |   |/  ___//  ___/  |  \_/ __ \|    |   \__  \ | __ \_/ __ \|  |
// |   |\___ \ \___ \|  |  /\  ___/|    |___ / __ \| \_\ \  ___/|  |__
// |___/____  >____  >____/  \___  >_______ (____  /___  /\___  >____/
//          \/     \/            \/        \/    \/    \/     \/

// IssueLabel represetns an issue-lable relation.
type IssueLabel struct {
	ID      int64 `xorm:"pk autoincr"`
	IssueID int64 `xorm:"UNIQUE(s)"`
	LabelID int64 `xorm:"UNIQUE(s)"`
}

func hasIssueLabel(e Engine, issueID, labelID int64) bool {
	has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
	return has
}

// HasIssueLabel returns true if issue has been labeled.
func HasIssueLabel(issueID, labelID int64) bool {
	return hasIssueLabel(x, issueID, labelID)
}

func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
	if _, err = e.Insert(&IssueLabel{
		IssueID: issue.ID,
		LabelID: label.ID,
	}); err != nil {
		return err
	}

	label.NumIssues++
	if issue.IsClosed {
		label.NumClosedIssues++
	}
	return updateLabel(e, label)
}

// NewIssueLabel creates a new issue-label relation.
func NewIssueLabel(issue *Issue, label *Label) (err error) {
	if HasIssueLabel(issue.ID, label.ID) {
		return nil
	}

	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = newIssueLabel(sess, issue, label); err != nil {
		return err
	}

	return sess.Commit()
}

func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) {
	for i := range labels {
		if hasIssueLabel(e, issue.ID, labels[i].ID) {
			continue
		}

		if err = newIssueLabel(e, issue, labels[i]); err != nil {
			return fmt.Errorf("newIssueLabel: %v", err)
		}
	}

	return nil
}

// NewIssueLabels creates a list of issue-label relations.
func NewIssueLabels(issue *Issue, labels []*Label) (err error) {
	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = newIssueLabels(sess, issue, labels); err != nil {
		return err
	}

	return sess.Commit()
}

func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
	issueLabels := make([]*IssueLabel, 0, 10)
	return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
}

// GetIssueLabels returns all issue-label relations of given issue by ID.
func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
	return getIssueLabels(x, issueID)
}

func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
	if _, err = e.Delete(&IssueLabel{
		IssueID: issue.ID,
		LabelID: label.ID,
	}); err != nil {
		return err
	}

	label.NumIssues--
	if issue.IsClosed {
		label.NumClosedIssues--
	}
	return updateLabel(e, label)
}

// DeleteIssueLabel deletes issue-label relation.
func DeleteIssueLabel(issue *Issue, label *Label) (err error) {
	sess := x.NewSession()
	defer sessionRelease(sess)
	if err = sess.Begin(); err != nil {
		return err
	}

	if err = deleteIssueLabel(sess, issue, label); err != nil {
		return err
	}

	return sess.Commit()
}
<|endoftext|>