<commit_before>package pebbleclient

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"mime"
	"net/http"
	"testing"
	"time"

	"golang.org/x/net/context"

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

var ctx = context.Background()

func TestNewHTTPClient_validWithDefaults(t *testing.T) {
	client, err := NewHTTPClient(Options{
		Host:        "localhost",
		ServiceName: "frobnitz",
	})
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "", client.GetOptions().Session)
	assert.Equal(t, "localhost", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}

func TestNewHTTPClient_validWithOptions(t *testing.T) {
	client, err := NewHTTPClient(Options{
		Host:        "localhost",
		Session:     "uio3ui3ui3",
		ServiceName: "frobnitz",
	})
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "uio3ui3ui3", client.GetOptions().Session)
	assert.Equal(t, "localhost", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}

func TestNewHTTPClient_invalidHost(t *testing.T) {
	_, err := NewHTTPClient(Options{Host: ""})
	assert.Error(t, err)
}

type Datum struct {
	Message string `json:"message"`
}

func TestClient_Get_plain(t *testing.T) {
	datum := &Datum{
		Message: "Say hello to my little friend",
	}

	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		assert.Equal(t, "GET", req.Method)

		w.Header().Set("Content-Type", "application/json")

		encoder := json.NewEncoder(w)
		encoder.Encode(datum)
	}))
	assert.NoError(t, err)
	defer server.Close()

	var result *Datum
	err = client.Get("hello", nil, &result)
	assert.NoError(t, err)
	assert.Equal(t, datum, result)
}

func TestClient_Get_errorStatusCodes(t *testing.T) {
	status := 400
	for status <= 599 {
		msgBytes := []byte("this failed")

		client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
			w.WriteHeader(status)
			w.Write(msgBytes)
		}))
		assert.NoError(t, err)
		defer server.Close()

		err = client.Get("hello", nil, &Datum{})
		if !assert.Error(t, err) {
			return
		}
		if !assert.IsType(t, &RequestError{}, err) {
			return
		}
		reqErr, ok := err.(*RequestError)
		if !assert.True(t, ok) {
			return
		}
		assert.Equal(t, status, reqErr.Resp.StatusCode)
		assert.Equal(t, msgBytes, reqErr.PartialBody)
		assert.Empty(t, reqErr.Options.Params)

		server.Close()

		status++
	}
}

func TestClient_Get_successStatusCodes(t *testing.T) {
	status := 200
	for status <= 299 {
		datum := &Datum{
			Message: "Say hello to my little friend",
		}

		client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)

			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(status)

			encoder := json.NewEncoder(w)
			encoder.Encode(datum)
		}))
		assert.NoError(t, err)
		defer server.Close()

		var result *Datum
		err = client.Get("hello", nil, &result)
		if !assert.NoError(t, err) {
			return
		}
		if status != http.StatusNoContent && status != http.StatusResetContent {
			assert.Equal(t, datum, result)
		}

		server.Close()

		status++
	}
}

func TestClient_Get_withParams(t *testing.T) {
	datum := &Datum{
		Message: "Say hello to my little friend",
	}

	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		assert.Equal(t, "json", req.URL.Query().Get("format"))

		w.Header().Set("Content-Type", "application/json")

		encoder := json.NewEncoder(w)
		encoder.Encode(datum)
	}))
	assert.NoError(t, err)
	defer server.Close()

	var result *Datum
	err = client.Get("hello", &RequestOptions{
		Params: Params{
			"format": "json",
		},
	}, &result)
	assert.NoError(t, err)
	assert.Equal(t, datum, result)
}

func TestClient_Get_withBeginningSlashPath(t *testing.T) {
	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		w.WriteHeader(200)
	}))
	assert.NoError(t, err)
	defer server.Close()

	err = client.Get("/hello", nil, nil)
	assert.NoError(t, err)
}

func TestClient_Get_withPathParams(t *testing.T) {
	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/get/drkropotkin", req.URL.Path)
		w.WriteHeader(200)
	}))
	assert.NoError(t, err)
	defer server.Close()

	err = client.Get("/get/:name", &RequestOptions{
		Params: Params{
			"name":   "drkropotkin",
			"format": "json",
		},
	}, nil)
	assert.NoError(t, err)
}

func TestClient_Get_badContentTypeInResponse(t *testing.T) {
	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		w.WriteHeader(200)
		w.Write([]byte(""))
	}))
	assert.NoError(t, err)
	defer server.Close()

	var result *Datum
	err = client.Get("hello", nil, &result)
	assert.Error(t, err)
}

func TestClient_Get_withLogging(t *testing.T) {
	logger := &MockLogger{}

	client, server, err := newClientAndServerWithOpts(Options{
		Logger: logger,
	}, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		time.Sleep(50e+6 * time.Nanosecond)
		w.WriteHeader(200)
		w.Write([]byte(`{"answer":42}`))
	}))
	assert.NoError(t, err)
	defer server.Close()

	err = client.Get("hello", &RequestOptions{}, nil)
	assert.NoError(t, err)
	assert.NotNil(t, logger.loggedReq)
	assert.Equal(t, "/api/frobnitz/v1/hello", logger.loggedReq.URL.Path)
	assert.NotNil(t, logger.loggedResp)
	assert.Equal(t, 200, logger.loggedResp.StatusCode)
	assert.Nil(t, logger.loggedErr)
	assert.True(t, logger.loggedDuration >= 50e+6)
}

func TestClient_Post_plain(t *testing.T) {
	datum := &Datum{
		Message: "Say hello to my little friend",
	}

	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		var mediaType string
		mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
		assert.NoError(t, err)
		assert.Equal(t, "application/json", mediaType)

		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		assert.Equal(t, "POST", req.Method)

		b, err := ioutil.ReadAll(req.Body)
		assert.NoError(t, err)
		assert.Equal(t, []byte(`{"message":"Say hello to my little friend"}`), b)

		w.Header().Set("Content-Type", "application/json")

		encoder := json.NewEncoder(w)
		encoder.Encode(datum)
	}))
	assert.NoError(t, err)
	defer server.Close()

	b, err := json.Marshal(datum)
	assert.NoError(t, err)

	var result *Datum
	err = client.Post("hello", &RequestOptions{}, bytes.NewReader(b), &result)
	assert.NoError(t, err)
	assert.Equal(t, datum, result)
}

func TestClient_FromHTTPRequest_cookie(t *testing.T) {
	req, err := http.NewRequest("GET", "http://example.com/", bytes.NewReader([]byte{}))
	assert.NoError(t, err)
	req.AddCookie(&http.Cookie{
		Name:  "checkpoint.session",
		Value: "uio3ui3ui3",
	})

	client, err := NewHTTPClient(Options{
		ServiceName: "frobnitz",
		Host:        "localhost",
	})
	assert.NoError(t, err)

	client, err = client.FromHTTPRequest(req)
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "uio3ui3ui3", client.GetOptions().Session)
	assert.Equal(t, "example.com", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}

func TestClient_FromHTTPRequest_sessionParam(t *testing.T) {
	req, err := http.NewRequest("GET", "http://example.com/?session=uio3ui3ui3", bytes.NewReader([]byte{}))
	assert.NoError(t, err)

	client, err := NewHTTPClient(Options{
		ServiceName: "frobnitz",
		Host:        "localhost",
	})
	assert.NoError(t, err)

	client, err = client.FromHTTPRequest(req)
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "uio3ui3ui3", client.GetOptions().Session)
	assert.Equal(t, "example.com", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}
<commit_msg>Remove outdated test.<commit_after>package pebbleclient

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"mime"
	"net/http"
	"testing"
	"time"

	"golang.org/x/net/context"

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

var ctx = context.Background()

func TestNewHTTPClient_validWithDefaults(t *testing.T) {
	client, err := NewHTTPClient(Options{
		Host:        "localhost",
		ServiceName: "frobnitz",
	})
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "", client.GetOptions().Session)
	assert.Equal(t, "localhost", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}

func TestNewHTTPClient_validWithOptions(t *testing.T) {
	client, err := NewHTTPClient(Options{
		Host:        "localhost",
		Session:     "uio3ui3ui3",
		ServiceName: "frobnitz",
	})
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "uio3ui3ui3", client.GetOptions().Session)
	assert.Equal(t, "localhost", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}

type Datum struct {
	Message string `json:"message"`
}

func TestClient_Get_plain(t *testing.T) {
	datum := &Datum{
		Message: "Say hello to my little friend",
	}

	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		assert.Equal(t, "GET", req.Method)

		w.Header().Set("Content-Type", "application/json")

		encoder := json.NewEncoder(w)
		encoder.Encode(datum)
	}))
	assert.NoError(t, err)
	defer server.Close()

	var result *Datum
	err = client.Get("hello", nil, &result)
	assert.NoError(t, err)
	assert.Equal(t, datum, result)
}

func TestClient_Get_errorStatusCodes(t *testing.T) {
	status := 400
	for status <= 599 {
		msgBytes := []byte("this failed")

		client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
			w.WriteHeader(status)
			w.Write(msgBytes)
		}))
		assert.NoError(t, err)
		defer server.Close()

		err = client.Get("hello", nil, &Datum{})
		if !assert.Error(t, err) {
			return
		}
		if !assert.IsType(t, &RequestError{}, err) {
			return
		}
		reqErr, ok := err.(*RequestError)
		if !assert.True(t, ok) {
			return
		}
		assert.Equal(t, status, reqErr.Resp.StatusCode)
		assert.Equal(t, msgBytes, reqErr.PartialBody)
		assert.Empty(t, reqErr.Options.Params)

		server.Close()

		status++
	}
}

func TestClient_Get_successStatusCodes(t *testing.T) {
	status := 200
	for status <= 299 {
		datum := &Datum{
			Message: "Say hello to my little friend",
		}

		client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)

			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(status)

			encoder := json.NewEncoder(w)
			encoder.Encode(datum)
		}))
		assert.NoError(t, err)
		defer server.Close()

		var result *Datum
		err = client.Get("hello", nil, &result)
		if !assert.NoError(t, err) {
			return
		}
		if status != http.StatusNoContent && status != http.StatusResetContent {
			assert.Equal(t, datum, result)
		}

		server.Close()

		status++
	}
}

func TestClient_Get_withParams(t *testing.T) {
	datum := &Datum{
		Message: "Say hello to my little friend",
	}

	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		assert.Equal(t, "json", req.URL.Query().Get("format"))

		w.Header().Set("Content-Type", "application/json")

		encoder := json.NewEncoder(w)
		encoder.Encode(datum)
	}))
	assert.NoError(t, err)
	defer server.Close()

	var result *Datum
	err = client.Get("hello", &RequestOptions{
		Params: Params{
			"format": "json",
		},
	}, &result)
	assert.NoError(t, err)
	assert.Equal(t, datum, result)
}

func TestClient_Get_withBeginningSlashPath(t *testing.T) {
	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		w.WriteHeader(200)
	}))
	assert.NoError(t, err)
	defer server.Close()

	err = client.Get("/hello", nil, nil)
	assert.NoError(t, err)
}

func TestClient_Get_withPathParams(t *testing.T) {
	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/get/drkropotkin", req.URL.Path)
		w.WriteHeader(200)
	}))
	assert.NoError(t, err)
	defer server.Close()

	err = client.Get("/get/:name", &RequestOptions{
		Params: Params{
			"name":   "drkropotkin",
			"format": "json",
		},
	}, nil)
	assert.NoError(t, err)
}

func TestClient_Get_badContentTypeInResponse(t *testing.T) {
	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "text/html")
		w.WriteHeader(200)
		w.Write([]byte(""))
	}))
	assert.NoError(t, err)
	defer server.Close()

	var result *Datum
	err = client.Get("hello", nil, &result)
	assert.Error(t, err)
}

func TestClient_Get_withLogging(t *testing.T) {
	logger := &MockLogger{}

	client, server, err := newClientAndServerWithOpts(Options{
		Logger: logger,
	}, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		time.Sleep(50e+6 * time.Nanosecond)
		w.WriteHeader(200)
		w.Write([]byte(`{"answer":42}`))
	}))
	assert.NoError(t, err)
	defer server.Close()

	err = client.Get("hello", &RequestOptions{}, nil)
	assert.NoError(t, err)
	assert.NotNil(t, logger.loggedReq)
	assert.Equal(t, "/api/frobnitz/v1/hello", logger.loggedReq.URL.Path)
	assert.NotNil(t, logger.loggedResp)
	assert.Equal(t, 200, logger.loggedResp.StatusCode)
	assert.Nil(t, logger.loggedErr)
	assert.True(t, logger.loggedDuration >= 50e+6)
}

func TestClient_Post_plain(t *testing.T) {
	datum := &Datum{
		Message: "Say hello to my little friend",
	}

	client, server, err := newClientAndServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		var mediaType string
		mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
		assert.NoError(t, err)
		assert.Equal(t, "application/json", mediaType)

		assert.Equal(t, "/api/frobnitz/v1/hello", req.URL.Path)
		assert.Equal(t, "POST", req.Method)

		b, err := ioutil.ReadAll(req.Body)
		assert.NoError(t, err)
		assert.Equal(t, []byte(`{"message":"Say hello to my little friend"}`), b)

		w.Header().Set("Content-Type", "application/json")

		encoder := json.NewEncoder(w)
		encoder.Encode(datum)
	}))
	assert.NoError(t, err)
	defer server.Close()

	b, err := json.Marshal(datum)
	assert.NoError(t, err)

	var result *Datum
	err = client.Post("hello", &RequestOptions{}, bytes.NewReader(b), &result)
	assert.NoError(t, err)
	assert.Equal(t, datum, result)
}

func TestClient_FromHTTPRequest_cookie(t *testing.T) {
	req, err := http.NewRequest("GET", "http://example.com/", bytes.NewReader([]byte{}))
	assert.NoError(t, err)
	req.AddCookie(&http.Cookie{
		Name:  "checkpoint.session",
		Value: "uio3ui3ui3",
	})

	client, err := NewHTTPClient(Options{
		ServiceName: "frobnitz",
		Host:        "localhost",
	})
	assert.NoError(t, err)

	client, err = client.FromHTTPRequest(req)
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "uio3ui3ui3", client.GetOptions().Session)
	assert.Equal(t, "example.com", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}

func TestClient_FromHTTPRequest_sessionParam(t *testing.T) {
	req, err := http.NewRequest("GET", "http://example.com/?session=uio3ui3ui3", bytes.NewReader([]byte{}))
	assert.NoError(t, err)

	client, err := NewHTTPClient(Options{
		ServiceName: "frobnitz",
		Host:        "localhost",
	})
	assert.NoError(t, err)

	client, err = client.FromHTTPRequest(req)
	assert.NoError(t, err)
	assert.NotNil(t, client)
	assert.Equal(t, "uio3ui3ui3", client.GetOptions().Session)
	assert.Equal(t, "example.com", client.GetOptions().Host)
	assert.Equal(t, "http", client.GetOptions().Protocol)
	assert.Equal(t, "frobnitz", client.GetOptions().ServiceName)
}
<|endoftext|>


<commit_before>package discorddotgo

import "github.com/bwmarrin/discordgo"

// Message wraps the message type with context
type Message struct {
	context    Context
	intMessage *discordgo.Message
}

func (m *Message) Text() string {
	return m.intMessage.Content
}

func (m *Message) ID() string {
	return m.intMessage.ID
}

func (m *Message) Respond(text string) (*Message, error) {
	msg, err := m.context.intSession.ChannelMessageSend(
		m.intMessage.ChannelID, text)
	return m.context.messageFromRaw(msg), err
}

func (m *Message) DisplayText() string {
	return m.intMessage.ContentWithMentionsReplaced()
}

func (m *Message) Author() *User {
	return m.context.userFromRaw(m.intMessage.Author)
}

func (m *Message) Channel() (*Channel, error) {
	ch, err := m.context.ChannelFromID(m.intMessage.ChannelID)
	if err != nil {
		return nil, err
	}
	return ch, nil
}

func (m *Message) Timestamp() string {
	return m.intMessage.Timestamp
}

func (m *Message) EditedTimestamp() string {
	return m.intMessage.EditedTimestamp
}

func (m *Message) Mentioned(u *User) bool {
	if m.intMessage.MentionEveryone {
		return true
	}

	for _, v := range m.intMessage.Mentions {
		if v.ID == u.ID() {
			return true
		}
	}

	return false
}

func (m *Message) Edit(newText string) (*Message, error) {
	ch, err := m.Channel()
	if err != nil {
		return nil, err
	}
	msg, err := m.context.int().ChannelMessageEdit(ch.ID(), m.ID(), newText)
	if err != nil {
		return nil, err
	}
	return m.context.messageFromRaw(msg), nil
}

func (m *Message) FromMe() bool {
	if m.Author().ID() == m.context.Self().ID() {
		return true
	}
	return false
}
<commit_msg>Messages now properly support mentions<commit_after>package discorddotgo

import "github.com/bwmarrin/discordgo"

// Message wraps the message type with context
type Message struct {
	context    Context
	intMessage *discordgo.Message
}

// Text returns the text of a message with mentions unreplaced.
func (m *Message) Text() string {
	return m.intMessage.Content
}

// ID returns the Message ID
func (m *Message) ID() string {
	return m.intMessage.ID
}

// Respond writes a message in the same channel the message originated from.
func (m *Message) Respond(text string) (*Message, error) {
	msg, err := m.context.intSession.ChannelMessageSend(
		m.intMessage.ChannelID, text)
	return m.context.messageFromRaw(msg), err
}

// DisplayText returns the text of a message with mentions replaced.
func (m *Message) DisplayText() string {
	return m.intMessage.ContentWithMentionsReplaced()
}

// AuthorUser returns the user that sent the message
func (m *Message) AuthorUser() *User {
	return m.context.userFromRaw(m.intMessage.Author)
}

// Author returns the Member that sent a message
func (m *Message) Author() (*Member, error) {
	u := m.AuthorUser()
	g, err := m.Guild()
	if err != nil {
		return nil, err
	}
	return u.Member(g)
}

// Channel returns the channel in which the message was sent or an error.
func (m *Message) Channel() (*Channel, error) {
	ch, err := m.context.ChannelFromID(m.intMessage.ChannelID)
	if err != nil {
		return nil, err
	}
	return ch, nil
}

// Guild returns the guild the message was send in. It returns an error
// if the lookup fails, for example if it's a private message channel.
func (m *Message) Guild() (*Guild, error) {
	ch, err := m.Channel()
	if err != nil {
		return nil, err
	}
	return ch.Guild()
}

// Timestamp returns the timestamp of a message
func (m *Message) Timestamp() string {
	return m.intMessage.Timestamp
}

// EditedTimestamp returns the timestamp of a message when it was edited
func (m *Message) EditedTimestamp() string {
	return m.intMessage.EditedTimestamp
}

// Mentioned returns true if the given user was mentioned.
// Note that it will not check against roles. Use MentionedMember for this.
func (m *Message) Mentioned(u *User) bool {
	if m.intMessage.MentionEveryone {
		return true
	}

	for _, v := range m.intMessage.Mentions {
		if v.ID == u.ID() {
			return true
		}
	}

	return false
}

// MentionedMember returns true if the specified member was mentioned.
// It might return an error in which case it will also return false.
func (m *Message) MentionedMember(member *Member) (bool, error) {
	res := m.Mentioned(member.User())
	if res {
		return true, nil
	}
	g, err := m.Guild()
	if err != nil {
		return false, err
	}
	for _, k := range m.intMessage.MentionRoles {
		role, err := m.context.RoleFromID(g.ID(), k)
		if err != nil {
			return false, err
		}
		if member.HasRole(role) {
			return true, nil
		}
	}
	return false, nil
}

// MentionedMe returns true if the current context user was mentioned.
// It might return an error in which case it will also return false.
func (m *Message) MentionedMe() (bool, error) {
	g, err := m.Guild()
	if err != nil {
		return false, err
	}
	mem, err := m.context.Self().Member(g)
	if err != nil {
		return false, err
	}
	return m.MentionedMember(mem)
}

// Edit edits the message and returns the new, edited message and an error
// if one occured.
func (m *Message) Edit(newText string) (*Message, error) {
	ch, err := m.Channel()
	if err != nil {
		return nil, err
	}
	msg, err := m.context.int().ChannelMessageEdit(ch.ID(), m.ID(), newText)
	if err != nil {
		return nil, err
	}
	return m.context.messageFromRaw(msg), nil
}

// FromMe returns true if the message is from the current user.
func (m *Message) FromMe() bool {
	if m.AuthorUser().ID() == m.context.Self().ID() {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package stun

import (
	"io"
	"testing"
	"time"
)

type NoopAgent struct {
	f chan AgentFn
}

func (n *NoopAgent) Close() error {
	close(n.f)
	return nil
}

func (NoopAgent) Collect(time.Time) error { return nil }

func (NoopAgent) Process(m *Message) error { return nil }

func (n *NoopAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error {
	n.f <- f
	return nil
}

func (n *NoopAgent) Stop([TransactionIDSize]byte) error {
	return nil
}

type noopConnection struct{}

func (noopConnection) Write(b []byte) (int, error) {
	return len(b), nil
}

func (noopConnection) Read(b []byte) (int, error) {
	time.Sleep(time.Millisecond)
	return 0, io.EOF
}

func (noopConnection) Close() error {
	return nil
}

func BenchmarkClient_Do(b *testing.B) {
	b.ReportAllocs()
	agent := &NoopAgent{
		f: make(chan AgentFn),
	}
	client := NewClient(ClientOptions{
		Agent:      agent,
		Connection: noopConnection{},
	})
	defer client.Close()
	go func() {
		e := AgentEvent{
			Error:   nil,
			Message: nil,
		}
		for f := range agent.f {
			f(e)
		}
	}()
	m := new(Message)
	m.Encode()
	noopF := func(event AgentEvent) {
		// pass
	}
	for i := 0; i < b.N; i++ {
		if err := client.Do(m, time.Time{}, noopF); err != nil {
			b.Fatal(err)
		}
	}
}
<commit_msg>client: improve test coverage (fix #33)<commit_after>package stun

import (
	"io"
	"sync"
	"testing"
	"time"
)

type TestAgent struct {
	f chan AgentFn
}

func (n *TestAgent) Close() error {
	close(n.f)
	return nil
}

func (TestAgent) Collect(time.Time) error { return nil }

func (TestAgent) Process(m *Message) error { return nil }

func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error {
	n.f <- f
	return nil
}

func (n *TestAgent) Stop([TransactionIDSize]byte) error {
	return nil
}

type noopConnection struct{}

func (noopConnection) Write(b []byte) (int, error) {
	return len(b), nil
}

func (noopConnection) Read(b []byte) (int, error) {
	time.Sleep(time.Millisecond)
	return 0, io.EOF
}

func (noopConnection) Close() error {
	return nil
}

func BenchmarkClient_Do(b *testing.B) {
	b.ReportAllocs()
	agent := &TestAgent{
		f: make(chan AgentFn),
	}
	client := NewClient(ClientOptions{
		Agent:      agent,
		Connection: noopConnection{},
	})
	defer client.Close()
	go func() {
		e := AgentEvent{
			Error:   nil,
			Message: nil,
		}
		for f := range agent.f {
			f(e)
		}
	}()
	m := new(Message)
	m.Encode()
	noopF := func(event AgentEvent) {
		// pass
	}
	for i := 0; i < b.N; i++ {
		if err := client.Do(m, time.Time{}, noopF); err != nil {
			b.Fatal(err)
		}
	}
}

type testConnection struct {
	write   func([]byte) (int, error)
	b       []byte
	l       sync.Mutex
	stopped bool
}

func (t *testConnection) Write(b []byte) (int, error) {
	t.l.Unlock()
	return t.write(b)
}

func (t *testConnection) Close() error {
	t.stopped = true
	t.l.Unlock()
	return nil
}

func (t *testConnection) Read(b []byte) (int, error) {
	t.l.Lock()
	if t.stopped {
		return 0, io.EOF
	}
	return copy(b, t.b), nil
}

func TestClosedOrPanic(t *testing.T) {
	closedOrPanic(nil)
	closedOrPanic(ErrAgentClosed)
	func() {
		defer func() {
			r := recover()
			if r != io.EOF {
				t.Error(r)
			}
		}()
		closedOrPanic(io.EOF)
	}()
}

func TestClient_Do(t *testing.T) {
	response := MustBuild(TransactionID, BindingSuccess)
	response.Encode()
	conn := &testConnection{
		b: response.Raw,
		write: func(bytes []byte) (int, error) {
			return len(bytes), nil
		},
	}
	conn.l.Lock()
	c := NewClient(ClientOptions{
		Connection: conn,
	})
	defer func() {
		if err := c.Close(); err != nil {
			t.Error(err)
		}
		if err := c.Close(); err == nil {
			t.Error("second close should fail")
		}
	}()
	m := new(Message)
	m.TransactionID = response.TransactionID
	m.Encode()
	d := time.Now().Add(time.Second)
	if err := c.Do(m, d, func(event AgentEvent) {
		if event.Error != nil {
			t.Error(event.Error)
		}
	}); err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"log"
	"strings"
	"time"

	apns "github.com/anachronistic/apns"
)

type CommandMsg struct {
	Command map[string]string      `json:"command"`
	Message map[string]interface{} `json:"message,omitempty"`
}

type Message struct {
	Event string                 `json:"event"`
	Data  map[string]interface{} `json:"data"`
	Time  int64                  `json:"time"`
}

func (this *CommandMsg) FromSocket(sock *Socket) {
	command, ok := this.Command["command"]
	if !ok {
		return
	}

	if DEBUG {
		log.Printf("Handling socket message of type %s\n", command)
	}

	switch strings.ToLower(command) {
	case "message":
		if !CLIENT_BROAD {
			return
		}

		if sock.Server.Store.StorageType == "redis" {
			this.forwardToRedis(sock.Server)
			return
		}

		this.sendMessage(sock.Server)

	case "setpage":
		page, ok := this.Command["page"]
		if !ok || page == "" {
			return
		}

		if sock.Page != "" {
			sock.Server.Store.UnsetPage(sock) //remove old page if it exists
		}

		sock.Page = page
		sock.Server.Store.SetPage(sock) // set new page
	}
}

func (this *CommandMsg) FromRedis(server *Server) {
	command, ok := this.Command["command"]
	if !ok {
		return
	}

	if DEBUG {
		log.Printf("Handling redis message of type %s\n", command)
	}

	switch strings.ToLower(command) {

	case "message":
		this.sendMessage(server)

	case "pushios":
		this.pushiOS(server)
	}
}

func (this *CommandMsg) formatMessage() (*Message, error) {
	event, e_ok := this.Message["event"].(string)
	data, b_ok := this.Message["data"].(map[string]interface{})

	if !b_ok || !e_ok {
		return nil, errors.New("Could not format message")
	}

	msg := &Message{event, data, time.Now().UTC().Unix()}

	return msg, nil
}

func (this *CommandMsg) sendMessage(server *Server) {
	user, userok := this.Command["user"]
	page, pageok := this.Command["page"]

	if userok {
		this.messageUser(user, page, server)
	} else if pageok {
		this.messagePage(page, server)
	} else {
		this.messageAll(server)
	}
}

func (this *CommandMsg) pushiOS(server *Server) {
	deviceToken, deviceToken_ok := this.Command["device_token"]
	build, _ := this.Command["build"]

	if !deviceToken_ok {
		log.Println("Device token not provided!")
		return
	}

	msg, err := this.formatMessage()
	if err != nil {
		log.Println("Could not format message")
		return
	}

	payload := apns.NewPayload()
	payload.Alert = msg.Data["message_text"]
	payload.Sound = server.Config.Get("ios_push_sound")
	payload.Badge = int(msg.Data["badge_count"].(float64))

	pn := apns.NewPushNotification()
	pn.DeviceToken = deviceToken
	pn.AddPayload(payload)
	pn.Set("payload", msg)

	var apns_url string
	var client *apns.Client

	switch build {

	case "store", "enterprise", "beta", "development":
		if build == "store" || build == "enterprise" || build == "beta" {
			apns_url = server.Config.Get("apns_production_url")
		} else {
			apns_url = server.Config.Get("apns_sandbox_url")
		}

		client = apns.NewClient(apns_url, server.Config.Get("apns_"+build+"_cert"), server.Config.Get("apns_"+build+"_private_key"))

	default:
		apns_url = server.Config.Get("apns_production_url")
		client = apns.NewClient(apns_url, server.Config.Get("apns_store_cert"), server.Config.Get("apns_store_private_key"))
	}

	resp := client.Send(pn)
	alert, _ := pn.PayloadString()

	if resp.Error != nil {
		log.Printf("Alert: %s\n", alert)
		log.Printf("Error: %s\n", resp.Error)
	}
}

func (this *CommandMsg) messageUser(UID string, page string, server *Server) {
	msg, err := this.formatMessage()
	if err != nil {
		return
	}

	user, err := server.Store.Client(UID)
	if err != nil {
		return
	}

	for _, sock := range user {
		if page != "" && page != sock.Page {
			continue
		}

		if !sock.isClosed() {
			sock.buff <- msg
		}
	}
}

func (this *CommandMsg) messageAll(server *Server) {
	msg, err := this.formatMessage()
	if err != nil {
		return
	}

	clients := server.Store.Clients()

	for _, user := range clients {
		for _, sock := range user {
			if !sock.isClosed() {
				sock.buff <- msg
			}
		}
	}

	return
}

func (this *CommandMsg) messagePage(page string, server *Server) {
	msg, err := this.formatMessage()
	if err != nil {
		return
	}

	pageMap := server.Store.getPage(page)
	if pageMap == nil {
		return
	}

	for _, sock := range pageMap {
		if !sock.isClosed() {
			sock.buff <- msg
		}
	}

	return
}

func (this *CommandMsg) forwardToRedis(server *Server) {
	msg_str, _ := json.Marshal(this)
	server.Store.redis.Publish(server.Config.Get("redis_message_channel"), string(msg_str)) //pass the message into redis to send message across cluster
}
<commit_msg>Flipping switch statement for APNS URL<commit_after>package main

import (
	"encoding/json"
	"errors"
	"log"
	"strings"
	"time"

	apns "github.com/anachronistic/apns"
)

type CommandMsg struct {
	Command map[string]string      `json:"command"`
	Message map[string]interface{} `json:"message,omitempty"`
}

type Message struct {
	Event string                 `json:"event"`
	Data  map[string]interface{} `json:"data"`
	Time  int64                  `json:"time"`
}

func (this *CommandMsg) FromSocket(sock *Socket) {
	command, ok := this.Command["command"]
	if !ok {
		return
	}

	if DEBUG {
		log.Printf("Handling socket message of type %s\n", command)
	}

	switch strings.ToLower(command) {
	case "message":
		if !CLIENT_BROAD {
			return
		}

		if sock.Server.Store.StorageType == "redis" {
			this.forwardToRedis(sock.Server)
			return
		}

		this.sendMessage(sock.Server)

	case "setpage":
		page, ok := this.Command["page"]
		if !ok || page == "" {
			return
		}

		if sock.Page != "" {
			sock.Server.Store.UnsetPage(sock) //remove old page if it exists
		}

		sock.Page = page
		sock.Server.Store.SetPage(sock) // set new page
	}
}

func (this *CommandMsg) FromRedis(server *Server) {
	command, ok := this.Command["command"]
	if !ok {
		return
	}

	if DEBUG {
		log.Printf("Handling redis message of type %s\n", command)
	}

	switch strings.ToLower(command) {

	case "message":
		this.sendMessage(server)

	case "pushios":
		this.pushiOS(server)
	}
}

func (this *CommandMsg) formatMessage() (*Message, error) {
	event, e_ok := this.Message["event"].(string)
	data, b_ok := this.Message["data"].(map[string]interface{})

	if !b_ok || !e_ok {
		return nil, errors.New("Could not format message")
	}

	msg := &Message{event, data, time.Now().UTC().Unix()}

	return msg, nil
}

func (this *CommandMsg) sendMessage(server *Server) {
	user, userok := this.Command["user"]
	page, pageok := this.Command["page"]

	if userok {
		this.messageUser(user, page, server)
	} else if pageok {
		this.messagePage(page, server)
	} else {
		this.messageAll(server)
	}
}

func (this *CommandMsg) pushiOS(server *Server) {
	deviceToken, deviceToken_ok := this.Command["device_token"]
	build, _ := this.Command["build"]

	if !deviceToken_ok {
		log.Println("Device token not provided!")
		return
	}

	msg, err := this.formatMessage()
	if err != nil {
		log.Println("Could not format message")
		return
	}

	payload := apns.NewPayload()
	payload.Alert = msg.Data["message_text"]
	payload.Sound = server.Config.Get("ios_push_sound")
	payload.Badge = int(msg.Data["badge_count"].(float64))

	pn := apns.NewPushNotification()
	pn.DeviceToken = deviceToken
	pn.AddPayload(payload)
	pn.Set("payload", msg)

	var apns_url string
	var client *apns.Client

	switch build {

	case "store", "enterprise", "beta", "development":
		if build == "development" {
			apns_url = server.Config.Get("apns_sandbox_url")
		} else {
			apns_url = server.Config.Get("apns_production_url")
		}

		client = apns.NewClient(apns_url, server.Config.Get("apns_"+build+"_cert"), server.Config.Get("apns_"+build+"_private_key"))

	default:
		apns_url = server.Config.Get("apns_production_url")
		client = apns.NewClient(apns_url, server.Config.Get("apns_store_cert"), server.Config.Get("apns_store_private_key"))
	}

	resp := client.Send(pn)
	alert, _ := pn.PayloadString()

	if resp.Error != nil {
		log.Printf("Alert: %s\n", alert)
		log.Printf("Error: %s\n", resp.Error)
	}
}

func (this *CommandMsg) messageUser(UID string, page string, server *Server) {
	msg, err := this.formatMessage()
	if err != nil {
		return
	}

	user, err := server.Store.Client(UID)
	if err != nil {
		return
	}

	for _, sock := range user {
		if page != "" && page != sock.Page {
			continue
		}

		if !sock.isClosed() {
			sock.buff <- msg
		}
	}
}

func (this *CommandMsg) messageAll(server *Server) {
	msg, err := this.formatMessage()
	if err != nil {
		return
	}

	clients := server.Store.Clients()

	for _, user := range clients {
		for _, sock := range user {
			if !sock.isClosed() {
				sock.buff <- msg
			}
		}
	}

	return
}

func (this *CommandMsg) messagePage(page string, server *Server) {
	msg, err := this.formatMessage()
	if err != nil {
		return
	}

	pageMap := server.Store.getPage(page)
	if pageMap == nil {
		return
	}

	for _, sock := range pageMap {
		if !sock.isClosed() {
			sock.buff <- msg
		}
	}

	return
}

func (this *CommandMsg) forwardToRedis(server *Server) {
	msg_str, _ := json.Marshal(this)
	server.Store.redis.Publish(server.Config.Get("redis_message_channel"), string(msg_str)) //pass the message into redis to send message across cluster
}
<|endoftext|>


<commit_before>package yagnats

import (
	. "launchpad.net/gocheck"
	"net"
	"os/exec"
	"testing"
	"time"
)

func Test(t *testing.T) { TestingT(t) }

type YSuite struct {
	Client  *Client
	NatsCmd *exec.Cmd
}

var _ = Suite(&YSuite{})

func (s *YSuite) SetUpSuite(c *C) {
	s.NatsCmd = startNats(4223)
	waitUntilNatsUp(4223)
}

func (s *YSuite) TearDownSuite(c *C) {
	stopCmd(s.NatsCmd)
}

func (s *YSuite) SetUpTest(c *C) {
	client := NewClient()

	client.Connect("127.0.0.1:4223", "nats", "nats")

	s.Client = client
}

func (s *YSuite) TearDownTest(c *C) {
	s.Client.Disconnect()
	s.Client = nil
}

func (s *YSuite) TestConnectWithInvalidAddress(c *C) {
	badClient := NewClient()

	err := badClient.Connect("", "cats", "bats")

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "dial tcp: missing address")
}

func (s *YSuite) TestClientConnectWithInvalidAuth(c *C) {
	badClient := NewClient()

	err := badClient.Connect("127.0.0.1:4223", "cats", "bats")

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Authorization failed")
}

func (s *YSuite) TestClientPing(c *C) {
	c.Assert(s.Client.Ping(), Equals, true)
}

func (s *YSuite) TestClientPingWhenNotConnected(c *C) {
	disconnectedClient := NewClient()
	c.Assert(disconnectedClient.Ping(), Equals, false)
}

func (s *YSuite) TestClientPingWhenConnectionClosed(c *C) {
	conn := <-s.Client.connection
	conn.Disconnect()
	c.Assert(s.Client.Ping(), Equals, false)
}

func (s *YSuite) TestClientPingWhenResponseIsTooSlow(c *C) {
	fakeConn := NewConnection("127.0.0.1:4223", "nats", "nats")

	conn, err := net.Dial("tcp", "127.0.0.1:4223")
	if err != nil {
		c.Error("Could not dial")
	}

	fakeConn.conn = conn

	disconnectedClient := NewClient()

	go func() {
		for {
			disconnectedClient.connection <- fakeConn
		}
	}()

	go func() {
		time.Sleep(1 * time.Second)
		fakeConn.PONGs <- &PongPacket{}
	}()

	c.Assert(disconnectedClient.Ping(), Equals, false)
}

func (s *YSuite) TestClientSubscribe(c *C) {
	sub, _ := s.Client.Subscribe("some.subject", func(msg *Message) {})
	c.Assert(sub, Equals, 1)

	sub2, _ := s.Client.Subscribe("some.subject", func(msg *Message) {})
	c.Assert(sub2, Equals, 2)
}

func (s *YSuite) TestClientUnsubscribe(c *C) {
	payload1 := make(chan string)
	payload2 := make(chan string)

	sid1, _ := s.Client.Subscribe("some.subject", func(msg *Message) {
		payload1 <- msg.Payload
	})

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload2 <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload1, 500)
	waitReceive(c, "hello!", payload2, 500)

	s.Client.Unsubscribe(sid1)

	s.Client.Publish("some.subject", "hello!")

	select {
	case <-payload1:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}

	waitReceive(c, "hello!", payload2, 500)
}

func (s *YSuite) TestClientUnsubscribeInvalid(c *C) {
	err := s.Client.Unsubscribe(42)

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Invalid Subject-Identifier (sid), no subscriber registered")
}

func (s *YSuite) TestClientSubscribeAndUnsubscribe(c *C) {
	payload := make(chan string)

	sid1, _ := s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)

	s.Client.Unsubscribe(sid1)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)

	select {
	case <-payload:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}
}

func (s *YSuite) TestClientAutoResubscribe(c *C) {
	doomedNats := startNats(4213)
	defer stopCmd(doomedNats)

	durableClient := NewClient()
	durableClient.Connect("127.0.0.1:4213", "nats", "nats")

	payload := make(chan string)

	durableClient.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	stopCmd(doomedNats)
	waitUntilNatsDown(4213)
	doomedNats = startNats(4213)
	defer stopCmd(doomedNats)

	waitUntilNatsUp(4213)

	durableClient.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)
}

func (s *YSuite) TestClientConnectCallback(c *C) {
	doomedNats := startNats(4213)
	defer stopCmd(doomedNats)

	connectionChannel := make(chan string)

	newClient := NewClient()
	newClient.ConnectedCallback = func() {
		connectionChannel <- "yo"
	}

	newClient.Connect("127.0.0.1:4213", "nats", "nats")

	waitReceive(c, "yo", connectionChannel, 500)
}

func (s *YSuite) TestClientReconnectCallback(c *C) {
	doomedNats := startNats(4213)
	defer stopCmd(doomedNats)

	connectionChannel := make(chan string)

	durableClient := NewClient()
	durableClient.ConnectedCallback = func() {
		connectionChannel <- "yo"
	}

	durableClient.Connect("127.0.0.1:4213", "nats", "nats")

	waitReceive(c, "yo", connectionChannel, 500)

	stopCmd(doomedNats)
	err := waitUntilNatsDown(4213)
	c.Assert(err, IsNil)

	doomedNats = startNats(4213)
	defer stopCmd(doomedNats)

	waitUntilNatsUp(4213)

	waitReceive(c, "yo", connectionChannel, 500)
}

func (s *YSuite) TestClientPublishTooBig(c *C) {
	payload := make([]byte, 10240000)
	err := s.Client.Publish("foo", string(payload))

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Payload size exceeded")
}

func (s *YSuite) TestClientPublishTooBigRecoverable(c *C) {
	payload := make([]byte, 10240000)

	err := s.Client.Publish("foo", string(payload))

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Payload size exceeded")

	err = s.Client.Publish("some.publish", "bar")

	c.Assert(err, Equals, nil)
}

func (s *YSuite) TestClientSubscribeInvalidSubject(c *C) {
	sid, err := s.Client.Subscribe(">.a", func(msg *Message) {})

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Invalid Subject")
	c.Assert(sid, Equals, -1)
}

func (s *YSuite) TestClientUnsubscribeAll(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)

	s.Client.UnsubscribeAll("some.subject")

	s.Client.Publish("some.subject", "hello!")

	select {
	case <-payload:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}
}

func (s *YSuite) TestClientPubSub(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)
}

func (s *YSuite) TestClientPublishWithReply(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.request", func(msg *Message) {
		s.Client.Publish(msg.ReplyTo, "response!")
	})

	s.Client.Subscribe("some.reply", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.PublishWithReplyTo("some.request", "hello!", "some.reply")

	waitReceive(c, "response!", payload, 500)
}

func (s *YSuite) TestClientDisconnect(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Disconnect()

	otherClient := NewClient()
	otherClient.Connect("127.0.0.1:4223", "nats", "nats")
	otherClient.Publish("some.subject", "hello!")

	select {
	case <-payload:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}
}

func (s *YSuite) TestClientInvalidMessage(c *C) {
	payload := make(chan string)

	sid, err := s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Subscribe("some.other.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	c.Assert(err, Equals, nil)

	delete(s.Client.subscriptions, sid)

	s.Client.Publish("some.subject", "hello!")
	s.Client.Publish("some.other.subject", "hello to other!")

	waitReceive(c, "hello to other!", payload, 500)
}

func (s *YSuite) TestClientLogging(c *C) {
  logger := &DefaultLogger{}
  s.Client.Logger = logger
  c.Assert(s.Client.Logger, Equals, logger)
}

func (s *YSuite) TestClientPassesLoggerToConnection(c *C) {
  logger := &DefaultLogger{}

	client := NewClient()
	client.Logger = logger

  conn, err := client.connect("127.0.0.1:4223", "nats", "nats")
  c.Assert(err, IsNil)

  c.Assert(conn.Logger, Equals, logger)
}

func waitReceive(c *C, expected string, from chan string, ms time.Duration) {
	select {
	case msg := <-from:
		c.Assert(msg, Equals, expected)
	case <-time.After(ms * time.Millisecond):
		c.Error("Timed out waiting for message.")
	}
}
<commit_msg>go fmt<commit_after>package yagnats

import (
	. "launchpad.net/gocheck"
	"net"
	"os/exec"
	"testing"
	"time"
)

func Test(t *testing.T) { TestingT(t) }

type YSuite struct {
	Client  *Client
	NatsCmd *exec.Cmd
}

var _ = Suite(&YSuite{})

func (s *YSuite) SetUpSuite(c *C) {
	s.NatsCmd = startNats(4223)
	waitUntilNatsUp(4223)
}

func (s *YSuite) TearDownSuite(c *C) {
	stopCmd(s.NatsCmd)
}

func (s *YSuite) SetUpTest(c *C) {
	client := NewClient()

	client.Connect("127.0.0.1:4223", "nats", "nats")

	s.Client = client
}

func (s *YSuite) TearDownTest(c *C) {
	s.Client.Disconnect()
	s.Client = nil
}

func (s *YSuite) TestConnectWithInvalidAddress(c *C) {
	badClient := NewClient()

	err := badClient.Connect("", "cats", "bats")

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "dial tcp: missing address")
}

func (s *YSuite) TestClientConnectWithInvalidAuth(c *C) {
	badClient := NewClient()

	err := badClient.Connect("127.0.0.1:4223", "cats", "bats")

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Authorization failed")
}

func (s *YSuite) TestClientPing(c *C) {
	c.Assert(s.Client.Ping(), Equals, true)
}

func (s *YSuite) TestClientPingWhenNotConnected(c *C) {
	disconnectedClient := NewClient()
	c.Assert(disconnectedClient.Ping(), Equals, false)
}

func (s *YSuite) TestClientPingWhenConnectionClosed(c *C) {
	conn := <-s.Client.connection
	conn.Disconnect()
	c.Assert(s.Client.Ping(), Equals, false)
}

func (s *YSuite) TestClientPingWhenResponseIsTooSlow(c *C) {
	fakeConn := NewConnection("127.0.0.1:4223", "nats", "nats")

	conn, err := net.Dial("tcp", "127.0.0.1:4223")
	if err != nil {
		c.Error("Could not dial")
	}

	fakeConn.conn = conn

	disconnectedClient := NewClient()

	go func() {
		for {
			disconnectedClient.connection <- fakeConn
		}
	}()

	go func() {
		time.Sleep(1 * time.Second)
		fakeConn.PONGs <- &PongPacket{}
	}()

	c.Assert(disconnectedClient.Ping(), Equals, false)
}

func (s *YSuite) TestClientSubscribe(c *C) {
	sub, _ := s.Client.Subscribe("some.subject", func(msg *Message) {})
	c.Assert(sub, Equals, 1)

	sub2, _ := s.Client.Subscribe("some.subject", func(msg *Message) {})
	c.Assert(sub2, Equals, 2)
}

func (s *YSuite) TestClientUnsubscribe(c *C) {
	payload1 := make(chan string)
	payload2 := make(chan string)

	sid1, _ := s.Client.Subscribe("some.subject", func(msg *Message) {
		payload1 <- msg.Payload
	})

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload2 <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload1, 500)
	waitReceive(c, "hello!", payload2, 500)

	s.Client.Unsubscribe(sid1)

	s.Client.Publish("some.subject", "hello!")

	select {
	case <-payload1:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}

	waitReceive(c, "hello!", payload2, 500)
}

func (s *YSuite) TestClientUnsubscribeInvalid(c *C) {
	err := s.Client.Unsubscribe(42)

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Invalid Subject-Identifier (sid), no subscriber registered")
}

func (s *YSuite) TestClientSubscribeAndUnsubscribe(c *C) {
	payload := make(chan string)

	sid1, _ := s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)

	s.Client.Unsubscribe(sid1)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)

	select {
	case <-payload:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}
}

func (s *YSuite) TestClientAutoResubscribe(c *C) {
	doomedNats := startNats(4213)
	defer stopCmd(doomedNats)

	durableClient := NewClient()
	durableClient.Connect("127.0.0.1:4213", "nats", "nats")

	payload := make(chan string)

	durableClient.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	stopCmd(doomedNats)
	waitUntilNatsDown(4213)
	doomedNats = startNats(4213)
	defer stopCmd(doomedNats)

	waitUntilNatsUp(4213)

	durableClient.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)
}

func (s *YSuite) TestClientConnectCallback(c *C) {
	doomedNats := startNats(4213)
	defer stopCmd(doomedNats)

	connectionChannel := make(chan string)

	newClient := NewClient()
	newClient.ConnectedCallback = func() {
		connectionChannel <- "yo"
	}

	newClient.Connect("127.0.0.1:4213", "nats", "nats")

	waitReceive(c, "yo", connectionChannel, 500)
}

func (s *YSuite) TestClientReconnectCallback(c *C) {
	doomedNats := startNats(4213)
	defer stopCmd(doomedNats)

	connectionChannel := make(chan string)

	durableClient := NewClient()
	durableClient.ConnectedCallback = func() {
		connectionChannel <- "yo"
	}

	durableClient.Connect("127.0.0.1:4213", "nats", "nats")

	waitReceive(c, "yo", connectionChannel, 500)

	stopCmd(doomedNats)
	err := waitUntilNatsDown(4213)
	c.Assert(err, IsNil)

	doomedNats = startNats(4213)
	defer stopCmd(doomedNats)

	waitUntilNatsUp(4213)

	waitReceive(c, "yo", connectionChannel, 500)
}

func (s *YSuite) TestClientPublishTooBig(c *C) {
	payload := make([]byte, 10240000)
	err := s.Client.Publish("foo", string(payload))

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Payload size exceeded")
}

func (s *YSuite) TestClientPublishTooBigRecoverable(c *C) {
	payload := make([]byte, 10240000)

	err := s.Client.Publish("foo", string(payload))

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Payload size exceeded")

	err = s.Client.Publish("some.publish", "bar")

	c.Assert(err, Equals, nil)
}

func (s *YSuite) TestClientSubscribeInvalidSubject(c *C) {
	sid, err := s.Client.Subscribe(">.a", func(msg *Message) {})

	c.Assert(err, Not(Equals), nil)
	c.Assert(err.Error(), Equals, "Invalid Subject")
	c.Assert(sid, Equals, -1)
}

func (s *YSuite) TestClientUnsubscribeAll(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)

	s.Client.UnsubscribeAll("some.subject")

	s.Client.Publish("some.subject", "hello!")

	select {
	case <-payload:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}
}

func (s *YSuite) TestClientPubSub(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Publish("some.subject", "hello!")

	waitReceive(c, "hello!", payload, 500)
}

func (s *YSuite) TestClientPublishWithReply(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.request", func(msg *Message) {
		s.Client.Publish(msg.ReplyTo, "response!")
	})

	s.Client.Subscribe("some.reply", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.PublishWithReplyTo("some.request", "hello!", "some.reply")

	waitReceive(c, "response!", payload, 500)
}

func (s *YSuite) TestClientDisconnect(c *C) {
	payload := make(chan string)

	s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Disconnect()

	otherClient := NewClient()
	otherClient.Connect("127.0.0.1:4223", "nats", "nats")
	otherClient.Publish("some.subject", "hello!")

	select {
	case <-payload:
		c.Error("Should not have received message.")
	case <-time.After(500 * time.Millisecond):
	}
}

func (s *YSuite) TestClientInvalidMessage(c *C) {
	payload := make(chan string)

	sid, err := s.Client.Subscribe("some.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	s.Client.Subscribe("some.other.subject", func(msg *Message) {
		payload <- msg.Payload
	})

	c.Assert(err, Equals, nil)

	delete(s.Client.subscriptions, sid)

	s.Client.Publish("some.subject", "hello!")
	s.Client.Publish("some.other.subject", "hello to other!")

	waitReceive(c, "hello to other!", payload, 500)
}

func (s *YSuite) TestClientLogging(c *C) {
	logger := &DefaultLogger{}
	s.Client.Logger = logger
	c.Assert(s.Client.Logger, Equals, logger)
}

func (s *YSuite) TestClientPassesLoggerToConnection(c *C) {
	logger := &DefaultLogger{}

	client := NewClient()
	client.Logger = logger

	conn, err := client.connect("127.0.0.1:4223", "nats", "nats")
	c.Assert(err, IsNil)

	c.Assert(conn.Logger, Equals, logger)
}

func waitReceive(c *C, expected string, from chan string, ms time.Duration) {
	select {
	case msg := <-from:
		c.Assert(msg, Equals, expected)
	case <-time.After(ms * time.Millisecond):
		c.Error("Timed out waiting for message.")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/gitpods/gitpods/authorization"
	"github.com/gitpods/gitpods/cmd"
	"github.com/gitpods/gitpods/repository"
	"github.com/gitpods/gitpods/resolver"
	"github.com/gitpods/gitpods/session"
	"github.com/gitpods/gitpods/storage"
	"github.com/gitpods/gitpods/user"
	"github.com/go-chi/chi"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/prometheus"
	_ "github.com/lib/pq"
	graphql "github.com/neelance/graphql-go"
	"github.com/neelance/graphql-go/relay"
	"github.com/oklog/oklog/pkg/group"
	prom "github.com/prometheus/client_golang/prometheus"
	jaeger "github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/config"
	"github.com/urfave/cli"
)

type apiConf struct {
	HTTPAddr        string
	HTTPPrivateAddr string
	APIPrefix       string
	DatabaseDriver  string
	DatabaseDSN     string
	LogJSON         bool
	LogLevel        string
	Secret          string
	StorageGRPCURL  string
	TracingURL      string
}

var (
	apiConfig = apiConf{}

	apiFlags = []cli.Flag{
		cli.StringFlag{
			Name:        cmd.FlagAPIPrefix,
			EnvVar:      cmd.EnvAPIPrefix,
			Usage:       "The prefix the api is serving from, default: /",
			Value:       "/",
			Destination: &apiConfig.APIPrefix,
		},
		cli.StringFlag{
			Name:        cmd.FlagDatabaseDriver,
			EnvVar:      cmd.EnvDatabaseDriver,
			Usage:       "The database driver to use: memory & postgres",
			Value:       "postgres",
			Destination: &apiConfig.DatabaseDriver,
		},
		cli.StringFlag{
			Name:        cmd.FlagDatabaseDSN,
			EnvVar:      cmd.EnvDatabaseDSN,
			Usage:       "The database connection data",
			Destination: &apiConfig.DatabaseDSN,
		},
		cli.StringFlag{
			Name:        cmd.FlagHTTPAddr,
			EnvVar:      cmd.EnvHTTPAddr,
			Usage:       "The address gitpods API runs on",
			Value:       ":3020",
			Destination: &apiConfig.HTTPAddr,
		},
		cli.StringFlag{
			Name:        cmd.FlagHTTPPrivateAddr,
			EnvVar:      cmd.EnvHTTPPrivateAddr,
			Usage:       "The address gitpods runs a http server only for internal access",
			Value:       ":3021",
			Destination: &apiConfig.HTTPPrivateAddr,
		},
		cli.BoolFlag{
			Name:        cmd.FlagLogJSON,
			EnvVar:      cmd.EnvLogJSON,
			Usage:       "The logger will log json lines",
			Destination: &apiConfig.LogJSON,
		},
		cli.StringFlag{
			Name:        cmd.FlagLogLevel,
			EnvVar:      cmd.EnvLogLevel,
			Usage:       "The log level to filter logs with before printing",
			Value:       "info",
			Destination: &apiConfig.LogLevel,
		},
		cli.StringFlag{
			Name:        cmd.FlagSecret,
			EnvVar:      cmd.EnvSecret,
			Usage:       "This secret is going to be used to generate cookies",
			Destination: &apiConfig.Secret,
		},
		cli.StringFlag{
			Name:        cmd.FlagStorageGRPCURL,
			EnvVar:      cmd.EnvStorageGRPCURL,
			Usage:       "The storage's gprc url to connect with",
			Destination: &apiConfig.StorageGRPCURL,
		},
		cli.StringFlag{
			Name:        cmd.FlagTracingURL,
			EnvVar:      cmd.EnvTracingURL,
			Usage:       "The url to send spans for tracing to",
			Destination: &apiConfig.TracingURL,
		},
	}
)

func apiAction(c *cli.Context) error {
	if apiConfig.Secret == "" {
		return errors.New("the secret for the api can't be empty")
	}

	logger := cmd.NewLogger(apiConfig.LogJSON, apiConfig.LogLevel)
	logger = log.WithPrefix(logger, "app", c.App.Name)

	apiMetrics := apiMetrics()

	if apiConfig.TracingURL != "" {
		traceConfig := config.Configuration{
			Sampler: &config.SamplerConfig{
				Type:  jaeger.SamplerTypeConst,
				Param: 1,
			},
			Reporter: &config.ReporterConfig{
				LocalAgentHostPort: apiConfig.TracingURL,
			},
		}

		traceCloser, err := traceConfig.InitGlobalTracer(c.App.Name)
		if err != nil {
			return err
		}
		defer traceCloser.Close()

		level.Info(logger).Log(
			"msg", "tracing enabled",
			"addr", apiConfig.TracingURL,
		)
	} else {
		level.Info(logger).Log("msg", "tracing is disabled, no url given")
	}

	//
	// Stores
	//
	var (
		repositories repository.Store
		sessions     session.Store
		users        user.Store
	)

	switch apiConfig.DatabaseDriver {
	default:
		db, err := sql.Open("postgres", apiConfig.DatabaseDSN)
		if err != nil {
			return err
		}
		defer db.Close()

		users = user.NewPostgresStore(db)
		sessions = session.NewPostgresStore(db)
		repositories = repository.NewPostgresStore(db)
	}

	//
	// Storage
	//
	storageClient, err := storage.NewClient(apiConfig.StorageGRPCURL)
	if err != nil {
		return err
	}

	//
	// Services
	//
	var ss session.Service
	ss = session.NewService(sessions)
	ss = session.NewMetricsService(ss, apiMetrics.SessionsCreated, apiMetrics.SessionsCleared)
	ss = session.NewTracingService(ss)

	var as authorization.Service
	as = authorization.NewService(users.(authorization.Store), ss)
	as = authorization.NewLoggingService(log.WithPrefix(logger, "service", "authorization"), as)
	as = authorization.NewMetricsService(apiMetrics.LoginAttempts, as)
	as = authorization.NewTracingService(as)

	var us user.Service
	us = user.NewService(users)
	us = user.NewLoggingService(log.WithPrefix(logger, "service", "user"), us)
	us = user.NewTracingService(us)

	var rs repository.Service
	rs = repository.NewService(repositories, storageClient)
	rs = repository.NewLoggingService(log.WithPrefix(logger, "service", "repository"), rs)
	rs = repository.NewTracingService(rs)

	//
	// Resolvers
	//
	res := &resolver.Resolver{
		RepositoryResolver: resolver.NewRepository(rs, us),
		TreeResolver:       resolver.NewTree(rs),
		UserResolver:       resolver.NewUser(rs, us),
	}
	schema := graphql.MustParseSchema(resolver.Schema, res)

	//
	// Router
	//
	router := chi.NewRouter()
	router.Use(cmd.NewRequestLogger(logger))

	// Wrap the router inside a Router handler to make it possible to listen on / or on /api.
	// Change via APIPrefix.
	router.Route(apiConfig.APIPrefix, func(router chi.Router) {
		router.Get("/", func(w http.ResponseWriter, r *http.Request) {
			w.Write(page)
		})

		router.Mount("/authorize", authorization.NewHandler(as))

		router.Group(func(router chi.Router) {
			router.Use(session.Authorized(ss))
			router.Mount("/query", &relay.Handler{Schema: schema})
		})
	})

	if apiConfig.APIPrefix != "/" {
		router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintln(w, "API is available at ", apiConfig.APIPrefix)
		})
	}

	router.NotFound(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte(`{"error":"Not Found"}`))
	})

	server := &http.Server{
		Addr:    apiConfig.HTTPAddr,
		Handler: router,
	}

	privateRouter := chi.NewRouter()
	privateRouter.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, http.StatusText(http.StatusOK))
	})
	privateRouter.Mount("/metrics", prom.UninstrumentedHandler())
	privateRouter.Get("/version", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "0.0.0") // TODO: Return json
	})

	privateServer := &http.Server{
		Addr:    apiConfig.HTTPPrivateAddr,
		Handler: privateRouter,
	}

	var gr group.Group
	{
		gr.Add(func() error {
			dur := time.Minute
			level.Info(logger).Log("msg", "starting session cleaner", "interval", dur)
			for {
				if _, err := ss.ClearSessions(context.TODO()); err != nil {
					return err
				}
				time.Sleep(dur)
			}
		}, func(err error) {
		})
	}
	{
		gr.Add(func() error {
			level.Info(logger).Log(
				"msg", "starting gitpods api",
				"addr", apiConfig.HTTPAddr,
			)
			return server.ListenAndServe()
		}, func(err error) {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			if err := server.Shutdown(ctx); err != nil {
				level.Error(logger).Log(
					"msg", "failed to shutdown http server gracefully",
					"err", err,
				)
				return
			}
			level.Info(logger).Log("msg", "http server shutdown gracefully")
		})
	}
	{
		gr.Add(func() error {
			level.Info(logger).Log(
				"msg", "starting internal gitpods api",
				"addr", apiConfig.HTTPPrivateAddr,
			)
			return privateServer.ListenAndServe()
		}, func(err error) {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			if err := privateServer.Shutdown(ctx); err != nil {
				level.Error(logger).Log(
					"msg", "failed to shutdown internal http server gracefully",
					"err", err,
				)
				return
			}
			level.Info(logger).Log("msg", "internal http server shutdown gracefully")
		})
	}

	return gr.Run()
}

type APIMetrics struct {
	LoginAttempts   metrics.Counter
	SessionsCreated metrics.Counter
	SessionsCleared metrics.Counter
}

func apiMetrics() *APIMetrics {
	namespace := "gitpods"

	return &APIMetrics{
		LoginAttempts: prometheus.NewCounterFrom(prom.CounterOpts{
			Namespace: namespace,
			Subsystem: "authentication",
			Name:      "login_attempts_total",
			Help:      "Number of login attempts that succeeded and failed",
		}, []string{"status"}),
		SessionsCreated: prometheus.NewCounterFrom(prom.CounterOpts{
			Namespace: namespace,
			Subsystem: "sessions",
			Name:      "created_total",
			Help:      "Number of created sessions",
		}, []string{}),
		SessionsCleared: prometheus.NewCounterFrom(prom.CounterOpts{
			Namespace: namespace,
			Subsystem: "sessions",
			Name:      "cleared_total",
			Help:      "Number of cleared sessions",
		}, []string{}),
	}
}

var page = []byte(`
<!DOCTYPE html>

		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.7.8/graphiql.css" />

Loading...

`)
<commit_msg>Move router route declaration into block<commit_after>package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/gitpods/gitpods/authorization"
	"github.com/gitpods/gitpods/cmd"
	"github.com/gitpods/gitpods/repository"
	"github.com/gitpods/gitpods/resolver"
	"github.com/gitpods/gitpods/session"
	"github.com/gitpods/gitpods/storage"
	"github.com/gitpods/gitpods/user"
	"github.com/go-chi/chi"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/prometheus"
	_ "github.com/lib/pq"
	graphql "github.com/neelance/graphql-go"
	"github.com/neelance/graphql-go/relay"
	"github.com/oklog/oklog/pkg/group"
	prom "github.com/prometheus/client_golang/prometheus"
	jaeger "github.com/uber/jaeger-client-go"
	"github.com/uber/jaeger-client-go/config"
	"github.com/urfave/cli"
)

type apiConf struct {
	HTTPAddr        string
	HTTPPrivateAddr string
	APIPrefix       string
	DatabaseDriver  string
	DatabaseDSN     string
	LogJSON         bool
	LogLevel        string
	Secret          string
	StorageGRPCURL  string
	TracingURL      string
}

var (
	apiConfig = apiConf{}

	apiFlags = []cli.Flag{
		cli.StringFlag{
			Name:        cmd.FlagAPIPrefix,
			EnvVar:      cmd.EnvAPIPrefix,
			Usage:       "The prefix the api is serving from, default: /",
			Value:       "/",
			Destination: &apiConfig.APIPrefix,
		},
		cli.StringFlag{
			Name:        cmd.FlagDatabaseDriver,
			EnvVar:      cmd.EnvDatabaseDriver,
			Usage:       "The database driver to use: memory & postgres",
			Value:       "postgres",
			Destination: &apiConfig.DatabaseDriver,
		},
		cli.StringFlag{
			Name:        cmd.FlagDatabaseDSN,
			EnvVar:      cmd.EnvDatabaseDSN,
			Usage:       "The database connection data",
			Destination: &apiConfig.DatabaseDSN,
		},
		cli.StringFlag{
			Name:        cmd.FlagHTTPAddr,
			EnvVar:      cmd.EnvHTTPAddr,
			Usage:       "The address gitpods API runs on",
			Value:       ":3020",
			Destination: &apiConfig.HTTPAddr,
		},
		cli.StringFlag{
			Name:        cmd.FlagHTTPPrivateAddr,
			EnvVar:      cmd.EnvHTTPPrivateAddr,
			Usage:       "The address gitpods runs a http server only for internal access",
			Value:       ":3021",
			Destination: &apiConfig.HTTPPrivateAddr,
		},
		cli.BoolFlag{
			Name:        cmd.FlagLogJSON,
			EnvVar:      cmd.EnvLogJSON,
			Usage:       "The logger will log json lines",
			Destination: &apiConfig.LogJSON,
		},
		cli.StringFlag{
			Name:        cmd.FlagLogLevel,
			EnvVar:      cmd.EnvLogLevel,
			Usage:       "The log level to filter logs with before printing",
			Value:       "info",
			Destination: &apiConfig.LogLevel,
		},
		cli.StringFlag{
			Name:        cmd.FlagSecret,
			EnvVar:      cmd.EnvSecret,
			Usage:       "This secret is going to be used to generate cookies",
			Destination: &apiConfig.Secret,
		},
		cli.StringFlag{
			Name:        cmd.FlagStorageGRPCURL,
			EnvVar:      cmd.EnvStorageGRPCURL,
			Usage:       "The storage's gprc url to connect with",
			Destination: &apiConfig.StorageGRPCURL,
		},
		cli.StringFlag{
			Name:        cmd.FlagTracingURL,
			EnvVar:      cmd.EnvTracingURL,
			Usage:       "The url to send spans for tracing to",
			Destination: &apiConfig.TracingURL,
		},
	}
)

func apiAction(c *cli.Context) error {
	if apiConfig.Secret == "" {
		return errors.New("the secret for the api can't be empty")
	}

	logger := cmd.NewLogger(apiConfig.LogJSON, apiConfig.LogLevel)
	logger = log.WithPrefix(logger, "app", c.App.Name)

	apiMetrics := apiMetrics()

	if apiConfig.TracingURL != "" {
		traceConfig := config.Configuration{
			Sampler: &config.SamplerConfig{
				Type:  jaeger.SamplerTypeConst,
				Param: 1,
			},
			Reporter: &config.ReporterConfig{
				LocalAgentHostPort: apiConfig.TracingURL,
			},
		}

		traceCloser, err := traceConfig.InitGlobalTracer(c.App.Name)
		if err != nil {
			return err
		}
		defer traceCloser.Close()

		level.Info(logger).Log(
			"msg", "tracing enabled",
			"addr", apiConfig.TracingURL,
		)
	} else {
		level.Info(logger).Log("msg", "tracing is disabled, no url given")
	}

	//
	// Stores
	//
	var (
		repositories repository.Store
		sessions     session.Store
		users        user.Store
	)

	switch apiConfig.DatabaseDriver {
	default:
		db, err := sql.Open("postgres", apiConfig.DatabaseDSN)
		if err != nil {
			return err
		}
		defer db.Close()

		users = user.NewPostgresStore(db)
		sessions = session.NewPostgresStore(db)
		repositories = repository.NewPostgresStore(db)
	}

	//
	// Storage
	//
	storageClient, err := storage.NewClient(apiConfig.StorageGRPCURL)
	if err != nil {
		return err
	}

	//
	// Services
	//
	var ss session.Service
	ss = session.NewService(sessions)
	ss = session.NewMetricsService(ss, apiMetrics.SessionsCreated, apiMetrics.SessionsCleared)
	ss = session.NewTracingService(ss)

	var as authorization.Service
	as = authorization.NewService(users.(authorization.Store), ss)
	as = authorization.NewLoggingService(log.WithPrefix(logger, "service", "authorization"), as)
	as = authorization.NewMetricsService(apiMetrics.LoginAttempts, as)
	as = authorization.NewTracingService(as)

	var us user.Service
	us = user.NewService(users)
	us = user.NewLoggingService(log.WithPrefix(logger, "service", "user"), us)
	us = user.NewTracingService(us)

	var rs repository.Service
	rs = repository.NewService(repositories, storageClient)
	rs = repository.NewLoggingService(log.WithPrefix(logger, "service", "repository"), rs)
	rs = repository.NewTracingService(rs)

	//
	// Resolvers
	//
	res := &resolver.Resolver{
		RepositoryResolver: resolver.NewRepository(rs, us),
		TreeResolver:       resolver.NewTree(rs),
		UserResolver:       resolver.NewUser(rs, us),
	}
	schema := graphql.MustParseSchema(resolver.Schema, res)

	//
	// Router
	//
	router := chi.NewRouter()
	{
		router.Use(cmd.NewRequestLogger(logger))

		// Wrap the router inside a Router handler to make it possible to listen on / or on /api.
		// Change via APIPrefix.
		router.Route(apiConfig.APIPrefix, func(router chi.Router) {
			router.Get("/", func(w http.ResponseWriter, r *http.Request) {
				w.Write(page)
			})

			router.Mount("/authorize", authorization.NewHandler(as))

			router.Group(func(router chi.Router) {
				router.Use(session.Authorized(ss))
				router.Mount("/query", &relay.Handler{Schema: schema})
			})
		})

		if apiConfig.APIPrefix != "/" {
			router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
				fmt.Fprintln(w, "API is available at ", apiConfig.APIPrefix)
			})
		}

		router.NotFound(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte(`{"error":"Not Found"}`))
		})
	}

	server := &http.Server{
		Addr:    apiConfig.HTTPAddr,
		Handler: router,
	}

	privateRouter := chi.NewRouter()
	privateRouter.Get("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, http.StatusText(http.StatusOK))
	})
	privateRouter.Mount("/metrics", prom.UninstrumentedHandler())
	privateRouter.Get("/version", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "0.0.0") // TODO: Return json
	})

	privateServer := &http.Server{
		Addr:    apiConfig.HTTPPrivateAddr,
		Handler: privateRouter,
	}

	var gr group.Group
	{
		gr.Add(func() error {
			dur := time.Minute
			level.Info(logger).Log("msg", "starting session cleaner", "interval", dur)
			for {
				if _, err := ss.ClearSessions(context.TODO()); err != nil {
					return err
				}
				time.Sleep(dur)
			}
		}, func(err error) {
		})
	}
	{
		gr.Add(func() error {
			level.Info(logger).Log(
				"msg", "starting gitpods api",
				"addr", apiConfig.HTTPAddr,
			)
			return server.ListenAndServe()
		}, func(err error) {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			if err := server.Shutdown(ctx); err != nil {
				level.Error(logger).Log(
					"msg", "failed to shutdown http server gracefully",
					"err", err,
				)
				return
			}
			level.Info(logger).Log("msg", "http server shutdown gracefully")
		})
	}
	{
		gr.Add(func() error {
			level.Info(logger).Log(
				"msg", "starting internal gitpods api",
				"addr", apiConfig.HTTPPrivateAddr,
			)
			return privateServer.ListenAndServe()
		}, func(err error) {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			if err := privateServer.Shutdown(ctx); err != nil {
				level.Error(logger).Log(
					"msg", "failed to shutdown internal http server gracefully",
					"err", err,
				)
				return
			}
			level.Info(logger).Log("msg", "internal http server shutdown gracefully")
		})
	}

	return gr.Run()
}

type APIMetrics struct {
	LoginAttempts   metrics.Counter
	SessionsCreated metrics.Counter
	SessionsCleared metrics.Counter
}

func apiMetrics() *APIMetrics {
	namespace := "gitpods"

	return &APIMetrics{
		LoginAttempts: prometheus.NewCounterFrom(prom.CounterOpts{
			Namespace: namespace,
			Subsystem: "authentication",
			Name:      "login_attempts_total",
			Help:      "Number of login attempts that succeeded and failed",
		}, []string{"status"}),
		SessionsCreated: prometheus.NewCounterFrom(prom.CounterOpts{
			Namespace: namespace,
			Subsystem: "sessions",
			Name:      "created_total",
			Help:      "Number of created sessions",
		}, []string{}),
		SessionsCleared: prometheus.NewCounterFrom(prom.CounterOpts{
			Namespace: namespace,
			Subsystem: "sessions",
			Name:      "cleared_total",
			Help:      "Number of cleared sessions",
		}, []string{}),
	}
}

var page = []byte(`
<!DOCTYPE html>

		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.7.8/graphiql.css" />

Loading...

`)
<|endoftext|>


<commit_before>// Copyright © 2017 Alexander Sosna <alexander@xxor.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	wal "github.com/xxorde/pgglaskugel/wal"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	// archiveCmd represents the archive command
	archiveCmd = &cobra.Command{
		Use:   "archive WAL_FILE...",
		Short: "Archives given WAL file(s)",
		Long: `This command archives given WAL file(s). This command can be used as an archive_command. The command to recover is "recover".
	Example: archive_command = "` + myName + ` archive %p"`,
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) < 1 {
				log.Fatal("No WAL file was defined!")
			}

			// Counter for WAL files
			count := 0

			// WaitGroup for workers
			var wg sync.WaitGroup

			// Iterate over every WAL file
			for _, walSource := range args {
				walName := filepath.Base(walSource)

				f, err := os.Open(walSource)
				if err != nil {
					log.Error("Can not open WAL file")
					log.Fatal(err)
				}

				walReader := io.ReadCloser(f)

				// Add one worker to our waiting group (for waiting later)
				wg.Add(1)

				// Start worker
				go compressEncryptStream(&walReader, walName, storeWalStream, &wg)

				count++
			}

			// Wait for workers to finish
			//(WAIT FIRST FOR THE WORKER OR WE CAN LOOSE DATA)
			wg.Wait()

			elapsed := time.Since(startTime)
			log.Info("Archived ", count, " WAL file(s) in ", elapsed)
		},
	}
)

func testWalSource(walSource string) (err error) {
	// Get size of backup
	file, err := os.Open(walSource)
	if err != nil {
		return err
	}
	defer file.Close()

	fi, err := file.Stat()
	if err != nil {
		return err
	}

	if fi.Size() < wal.MinArchiveSize {
		return errors.New("Input file to small")
	}

	if fi.Size() > wal.MaxWalSize {
		return errors.New("Input file to big")
	}

	return nil
}

// storeWalStream takes a stream and persists it with the configured method
func storeWalStream(input *io.Reader, name string) {
	archiveTo := viper.GetString("archive_to")
	switch archiveTo {
	case "file":
		writeStreamToFile(input, filepath.Join(archiveDir, name))
	case "s3":
		writeStreamToS3(input, viper.GetString("s3_bucket_wal"), name)
	default:
		log.Fatal(archiveTo, " no valid value for archiveTo")
	}
}

func init() {
	RootCmd.AddCommand(archiveCmd)
}
<commit_msg>Fix archive dir followup<commit_after>// Copyright © 2017 Alexander Sosna <alexander@xxor.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	wal "github.com/xxorde/pgglaskugel/wal"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	// archiveCmd represents the archive command
	archiveCmd = &cobra.Command{
		Use:   "archive WAL_FILE...",
		Short: "Archives given WAL file(s)",
		Long: `This command archives given WAL file(s). This command can be used as an archive_command. The command to recover is "recover".
	Example: archive_command = "` + myName + ` archive %p"`,
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) < 1 {
				log.Fatal("No WAL file was defined!")
			}

			// Counter for WAL files
			count := 0

			// WaitGroup for workers
			var wg sync.WaitGroup

			// Iterate over every WAL file
			for _, walSource := range args {
				walName := filepath.Base(walSource)

				f, err := os.Open(walSource)
				if err != nil {
					log.Error("Can not open WAL file")
					log.Fatal(err)
				}

				walReader := io.ReadCloser(f)

				// Add one worker to our waiting group (for waiting later)
				wg.Add(1)

				// Start worker
				go compressEncryptStream(&walReader, walName, storeWalStream, &wg)

				count++
			}

			// Wait for workers to finish
			//(WAIT FIRST FOR THE WORKER OR WE CAN LOOSE DATA)
			wg.Wait()

			elapsed := time.Since(startTime)
			log.Info("Archived ", count, " WAL file(s) in ", elapsed)
		},
	}
)

func testWalSource(walSource string) (err error) {
	// Get size of backup
	file, err := os.Open(walSource)
	if err != nil {
		return err
	}
	defer file.Close()

	fi, err := file.Stat()
	if err != nil {
		return err
	}

	if fi.Size() < wal.MinArchiveSize {
		return errors.New("Input file to small")
	}

	if fi.Size() > wal.MaxWalSize {
		return errors.New("Input file to big")
	}

	return nil
}

// storeWalStream takes a stream and persists it with the configured method
func storeWalStream(input *io.Reader, name string) {
	archiveTo := viper.GetString("archive_to")
	switch archiveTo {
	case "file":
		writeStreamToFile(input, filepath.Join(walDir, name))
	case "s3":
		writeStreamToS3(input, viper.GetString("s3_bucket_wal"), name)
	default:
		log.Fatal(archiveTo, " no valid value for archiveTo")
	}
}

func init() {
	RootCmd.AddCommand(archiveCmd)
}
<|endoftext|>


<commit_before>// Copyright 2015, 2016 Eris Industries (UK) Ltd.
// This file is part of Eris-RT

// Eris-RT is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Eris-RT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.

package commands

import (
  "fmt"
  "os"

  cobra "github.com/spf13/cobra"

  common "github.com/eris-ltd/common/go/common"

  defintion "github.com/eris-ltd/eris-db/defintions"
  version   "github.com/eris-ltd/eris-db/version"
)

const VERSION = version.VERSION

// Global Do struct
var do *definitions.Do

var ErisDbCmd = &cobra.Command {
  Use:   "eris-db"
  Short: "Eris-DB is the beating heart of the eris chain."
  Long:  `Eris-DB is the beating heart of the eris chain.  Eris-DB combines
a modular consensus engine and application manager to run a chain to suit
your needs.

Made with <3 by Eris Industries.

Complete documentation is available at https://docs.erisindustries.com
` + "\nVERSION:\n " + VERSION,
  PersistentPreRun: func(cmd *cobra.Command, args [string]) {
    // TODO: [ben] set up eris logger after glide resolution of logrus
  },
  Run: func(cmd *cobra.Command, args []string) { cmd.Help() },
}

func Execute() {
  InitErisDb()
  AddGlobalFlags()
  AddCommands()
  ErisDbCmd.Execute()
}

func InitErisDb() {
  // initialise an empty do struct for command execution
	do = definitions.NowDo()
}

func AddCommands() {
  buildServeCommand()
  ErisDbCmd.AddCommand()
}

func AddGlobalFlags() {
	ErisDbCmd.PersistentFlags().BoolVarP(&do.Verbose, "verbose", "v", defaultVerbose(), "verbose output; more output than no output flags; less output than debug level; default respects $ERIS_DB_VERBOSE")
	ErisDbCmd.PersistentFlags().BoolVarP(&do.Debug, "debug", "d", defaultDebug(), "debug level output; the most output available for eris-db; if it is too chatty use verbose flag; default respects $ERIS_DB_DEBUG")
	ErisDbCmd.PersistentFlags().BoolVarP(&do.Output, "output", "o", defaultOutput(), "should eris-db provide an output of its execution; default respects $ERIS_DB_OUTPUT")
}

//------------------------------------------------------------------------------
// Defaults

func defaultVerbose() bool {
  return setDefaultBool("ERIS_DB_VERBOSE", false)
}

func defaultDebug() bool {
  return setDefaultBool("ERIS_DB_DEBUG", false)
}

func defaultOutput() bool {
  return setDefaultBool("ERIS_DB_OUTPUT", true)
}

func setDefaultBool(envVar string, def bool) bool {
	env := os.Getenv(envVar)
	if env != "" {
		i, _ := strconv.ParseBool(env)
		return i
	}
	return def
}
<commit_msg>comment on default global flags<commit_after>// Copyright 2015, 2016 Eris Industries (UK) Ltd.
// This file is part of Eris-RT

// Eris-RT is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Eris-RT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Eris-RT.  If not, see <http://www.gnu.org/licenses/>.

package commands

import (
  "fmt"
  "os"

  cobra "github.com/spf13/cobra"

  common "github.com/eris-ltd/common/go/common"

  defintion "github.com/eris-ltd/eris-db/defintions"
  version   "github.com/eris-ltd/eris-db/version"
)

const VERSION = version.VERSION

// Global Do struct
var do *definitions.Do

var ErisDbCmd = &cobra.Command {
  Use:   "eris-db"
  Short: "Eris-DB is the beating heart of the eris chain."
  Long:  `Eris-DB is the beating heart of the eris chain.  Eris-DB combines
a modular consensus engine and application manager to run a chain to suit
your needs.

Made with <3 by Eris Industries.

Complete documentation is available at https://docs.erisindustries.com
` + "\nVERSION:\n " + VERSION,
  PersistentPreRun: func(cmd *cobra.Command, args [string]) {
    // TODO: [ben] set up eris logger after glide resolution of logrus
  },
  Run: func(cmd *cobra.Command, args []string) { cmd.Help() },
}

func Execute() {
  InitErisDb()
  AddGlobalFlags()
  AddCommands()
  ErisDbCmd.Execute()
}

func InitErisDb() {
  // initialise an empty do struct for command execution
	do = definitions.NowDo()
}

func AddCommands() {
  buildServeCommand()
  ErisDbCmd.AddCommand()
}

func AddGlobalFlags() {
	ErisDbCmd.PersistentFlags().BoolVarP(&do.Verbose, "verbose", "v", defaultVerbose(), "verbose output; more output than no output flags; less output than debug level; default respects $ERIS_DB_VERBOSE")
	ErisDbCmd.PersistentFlags().BoolVarP(&do.Debug, "debug", "d", defaultDebug(), "debug level output; the most output available for eris-db; if it is too chatty use verbose flag; default respects $ERIS_DB_DEBUG")
	ErisDbCmd.PersistentFlags().BoolVarP(&do.Output, "output", "o", defaultOutput(), "should eris-db provide an output of its execution; default respects $ERIS_DB_OUTPUT")
}

//------------------------------------------------------------------------------
// Defaults

// defaultVerbose is set to false unless the ERIS_DB_VERBOSE environment
// variable is set to a parsable boolean.
func defaultVerbose() bool {
  return setDefaultBool("ERIS_DB_VERBOSE", false)
}

// defaultDebug is set to false unless the ERIS_DB_DEBUG environment
// variable is set to a parsable boolean.
func defaultDebug() bool {
  return setDefaultBool("ERIS_DB_DEBUG", false)
}

// defaultOutput is set to true unless the ERIS_DB_OUTPUT environment
// variable is set to a parsable boolean.
func defaultOutput() bool {
  return setDefaultBool("ERIS_DB_OUTPUT", true)
}

// setDefaultBool returns the provided default value if the environment variab;e
// is not set or not parsable as a bool.
func setDefaultBool(environmentVariable string, defaultValue bool) bool {
	value := os.Getenv(environmentVariable)
	if value != "" {
		if parsedValue, err := strconv.ParseBool(value); err == nil {
  		return parsedValue
    }
	}
	return defaultValue
}

func setDefaultString(envVar, def string) string {
	env := os.Getenv(envVar)
	if env != "" {
		return env
	}
	return def
}

func setDefaultStringSlice(envVar string, def []string) []string {
	env := os.Getenv(envVar)
	if env != "" {
		return strings.Split(env, ",")
	}
	return def
}
<|endoftext|>