<commit_before>package models

import (
	"fmt"
	"regexp"
	"time"

	"github.com/gocql/gocql"
	"github.com/skeswa/gophr/common/errors"
)

// Constants directly related to interacting with the package model in the
// cassandra database.
const (
	// TableNamePackages is the name of the table containing the package model.
	TableNamePackages = "packages"
	// IndexNamePackages is the name of the lucene index
	IndexNamePackages             = "packages_index"
	ColumnNamePackagesRepo        = "repo"
	ColumnNamePackagesStars       = "stars"
	ColumnNamePackagesExists      = "exists"
	ColumnNamePackagesAuthor      = "author"
	ColumnNamePackagesVersions    = "versions"
	ColumnNamePackagesGodocURL    = "godoc_url"
	ColumnNamePackagesIndexTime   = "index_time"
	ColumnNamePackagesAwesomeGo   = "awesome_go"
	ColumnNamePackagesSearchBlob  = "search_blob"
	ColumnNamePackagesDescription = "description"
)

const (
	packagesSearchBlobTemplate = "%s %s %s"
)

var (
	cqlQueryFuzzySearchPackagesTemplate = fmt.Sprintf(
		`SELECT %s,%s,%s FROM %s WHERE expr(%s,'{query:{type:"fuzzy",field:"%s",value:"%s"}}') LIMIT 10`,
		ColumnNamePackagesRepo,
		ColumnNamePackagesAuthor,
		ColumnNamePackagesDescription,
		TableNamePackages,
		IndexNamePackages,
		ColumnNamePackagesSearchBlob,
		"%s",
	)

	cqlQuerySelectPackageVersions = fmt.Sprintf(
		`SELECT %s FROM %s WHERE %s = ? AND %s = ? LIMIT 1`,
		ColumnNamePackagesVersions,
		TableNamePackages,
		ColumnNamePackagesAuthor,
		ColumnNamePackagesRepo,
	)

	cqlQueryInsertPackage = fmt.Sprintf(
		`INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES (?,?,?,?,?,?,?,?,?,?)`,
		TableNamePackages,
		ColumnNamePackagesRepo,
		ColumnNamePackagesStars,
		ColumnNamePackagesExists,
		ColumnNamePackagesAuthor,
		ColumnNamePackagesVersions,
		ColumnNamePackagesGodocURL,
		ColumnNamePackagesIndexTime,
		ColumnNamePackagesAwesomeGo,
		ColumnNamePackagesSearchBlob,
		ColumnNamePackagesDescription,
	)
)

var (
	alphanumericFilterRegex = regexp.MustCompile(`[^\sa-zA-Z0-9\-_]+`)
)

// PackageModel is a struct representing one individual package in the database.
type PackageModel struct {
	Repo        *string
	Stars       *int
	Exists      *bool
	Author      *string
	Versions    []string
	GodocURL    *string
	IndexTime   *time.Time
	AwesomeGo   *bool
	SearchBlob  *string
	Description *string
}

// NewPackageModelForInsert creates an instance of PackageModel that is
// optimized and validated for the insert operation in the database.
func NewPackageModelForInsert(
	author string,
	exists bool,
	repo string,
	versions []string,
	godocURL string,
	indexTime time.Time,
	awesomeGo bool,
	description string,
	stars int,
) (*PackageModel, error) {
	if len(repo) < 1 {
		return nil, errors.NewInvalidParameterError("repo", repo)
	}
	if len(author) < 1 {
		return nil, errors.NewInvalidParameterError("author", author)
	}
	if len(godocURL) < 1 {
		return nil, errors.NewInvalidParameterError("godocURL", godocURL)
	}

	searchBlob := fmt.Sprintf(
		packagesSearchBlobTemplate,
		author,
		repo,
		description,
	)

	return &PackageModel{
		Repo:        &repo,
		Stars:       &stars,
		Exists:      &exists,
		Author:      &author,
		Versions:    versions,
		GodocURL:    &godocURL,
		IndexTime:   &indexTime,
		AwesomeGo:   &awesomeGo,
		SearchBlob:  &searchBlob,
		Description: &description,
	}, nil
}

// NewPackageModelFromBulkSelect creates an instance of PackageModel that is
// optimized and validated for a select operation designed to get data about
// multiple packages from the database.
func NewPackageModelFromBulkSelect(
	author string,
	repo string,
	description string,
) (*PackageModel, error) {
	if len(repo) < 1 {
		return nil, errors.NewInvalidParameterError("repo", repo)
	}
	if len(author) < 1 {
		return nil, errors.NewInvalidParameterError("author", author)
	}

	return &PackageModel{
		Repo:        &repo,
		Author:      &author,
		Description: &description,
	}, nil
}

func NewPackageModelTest(
	author string,
	repo string,
	awesome_go bool,
	description string,
	exists bool,
	godoc_url string,
	index_time time.Time,
	search_blob string,
	versions []string,
	stars int,
) *PackageModel {
	return &PackageModel{
		Repo:        &repo,
		Stars:       &stars,
		Exists:      &exists,
		Author:      &author,
		Versions:    versions,
		GodocURL:    &godoc_url,
		IndexTime:   &index_time,
		AwesomeGo:   &awesome_go,
		SearchBlob:  &search_blob,
		Description: &description,
	}
}

// NewPackageModelFromSingleSelect creates an instance of PackageModel that is
// optimized and validated for a select operation designed to get data about
// a single package from the database.
func NewPackageModelFromSingleSelect(
	author string,
	exists bool,
	repo string,
	versions []string,
	godocURL string,
	awesomeGo bool,
	description string,
) (*PackageModel, error) {
	if len(repo) < 1 {
		return nil, errors.NewInvalidParameterError("repo", repo)
	}
	if len(author) < 1 {
		return nil, errors.NewInvalidParameterError("author", author)
	}
	if len(godocURL) < 1 {
		return nil, errors.NewInvalidParameterError("godocURL", godocURL)
	}

	return &PackageModel{
		Repo:        &repo,
		Exists:      &exists,
		Author:      &author,
		Versions:    versions,
		GodocURL:    &godocURL,
		AwesomeGo:   &awesomeGo,
		Description: &description,
	}, nil
}

// FindPackageVersions gets the versions of a package from the database. If
// no such package exists, or there were no versions for said package, then nil
// is returned.
func FindPackageVersions(session *gocql.Session, author string, repo string) ([]string, error) {
	var (
		err      error
		versions []string
	)

	iter := session.Query(cqlQuerySelectPackageVersions, author, repo).Iter()

	if !iter.Scan(&versions) {
		return nil, nil
	}

	if err = iter.Close(); err != nil {
		return nil, errors.NewQueryScanError(nil, err)
	}

	return versions, nil
}

// FuzzySearchPackages finds a list of packages relevant to a query phrase
// string. The search takes author, package and description into account.
func FuzzySearchPackages(
	session *gocql.Session,
	searchText string,
) ([]*PackageModel, error) {
	// First, remove all non-essential characters
	searchText = alphanumericFilterRegex.ReplaceAllString(searchText, "")
	// Next put the search text into a query string
	query := fmt.Sprintf(cqlQueryFuzzySearchPackagesTemplate, searchText)
	// Return the processed results of the query
	return scanPackageModels(session.Query(query))
}

// InsertPackage inserts an individual package into the database.
func InsertPackage(
	session *gocql.Session,
	packageModel *PackageModel,
) error {
	err := session.Query(cqlQueryInsertPackage,
		*packageModel.Repo,
		*packageModel.Stars,
		*packageModel.Exists,
		*packageModel.Author,
		packageModel.Versions,
		*packageModel.GodocURL,
		*packageModel.IndexTime,
		*packageModel.AwesomeGo,
		*packageModel.SearchBlob,
		*packageModel.Description,
	).Exec()

	return err
}

// InsertPackages inserts a slice of package models into the database.
func InsertPackages(
	session *gocql.Session,
	packageModels []*PackageModel,
) error {
	batch := gocql.NewBatch(gocql.LoggedBatch)

	if packageModels == nil || len(packageModels) == 0 {
		return errors.NewInvalidParameterError("packageModels", packageModels)
	}

	for _, packageModel := range packageModels {
		if packageModel != nil &&
			packageModel.Repo != nil &&
			packageModel.Exists != nil &&
			packageModel.Author != nil &&
			packageModel.GodocURL != nil &&
			packageModel.IndexTime != nil &&
			packageModel.AwesomeGo != nil &&
			packageModel.SearchBlob != nil &&
			packageModel.Description != nil {
			batch.Query(
				cqlQueryInsertPackage,
				*packageModel.Repo,
				*packageModel.Exists,
				*packageModel.Author,
				packageModel.Versions,
				*packageModel.GodocURL,
				*packageModel.IndexTime,
				*packageModel.AwesomeGo,
				*packageModel.SearchBlob,
				*packageModel.Description,
			)
		} else {
			return errors.NewInvalidParameterError(
				"packageModels",
				fmt.Sprintf("[ ..., %v, ... ]", packageModel),
			)
		}
	}

	err := session.ExecuteBatch(batch)
	if err != nil {
		return err
	}

	return nil
}

/********************************** HELPERS ***********************************/

// TODO(skeswa): implement this for querying single packages
func scanPackageModel(query *gocql.Query) ([]*PackageModel, error) {
	return nil, nil
}

func scanPackageModels(query *gocql.Query) ([]*PackageModel, error) {
	var (
		err          error
		scanError    error
		closeError   error
		packageModel *PackageModel

		repo        string
		author      string
		description string

		iter          = query.Iter()
		packageModels = make([]*PackageModel, 0)
	)

	for iter.Scan(&repo, &author, &description) {
		packageModel, err = NewPackageModelFromBulkSelect(author, repo, description)
		if err != nil {
			scanError = err
			break
		} else {
			packageModels = append(packageModels, packageModel)
		}
	}

	if err = iter.Close(); err != nil {
		closeError = err
	}

	if scanError != nil || closeError != nil {
		return nil, errors.NewQueryScanError(scanError, closeError)
	}

	return packageModels, nil
}

func ScanAllPackageModels(session *gocql.Session) ([]*PackageModel, error) {
	var (
		err          error
		scanError    error
		closeError   error
		packageModel *PackageModel

		author      string
		repo        string
		awesome_go  bool
		description string
		exists      bool
		godoc_url   string
		index_time  time.Time
		search_blob string
		versions    []string
		stars       int

		query = session.Query(`SELECT
			author,
			repo,
			awesome_go,
			description,
			exists,
			godoc_url,
			index_time,
			search_blob,
			versions,
			stars
			FROM packages`)
		iter          = query.Iter()
		packageModels = make([]*PackageModel, 0)
	)

	for iter.Scan(&author, &repo, &awesome_go, &description, &exists, &godoc_url, &index_time, &search_blob, &versions, &stars) {
		packageModel = NewPackageModelTest(author, repo, awesome_go, description, exists, godoc_url, index_time, search_blob, versions, stars)
		packageModels = append(packageModels, packageModel)
	}

	if err = iter.Close(); err != nil {
		closeError = err
	}

	if scanError != nil || closeError != nil {
		return nil, errors.NewQueryScanError(scanError, closeError)
	}

	return packageModels, nil
}
<commit_msg>add keyspace name to query<commit_after>package models

import (
	"fmt"
	"regexp"
	"time"

	"github.com/gocql/gocql"
	"github.com/skeswa/gophr/common/errors"
)

// Constants directly related to interacting with the package model in the
// cassandra database.
const (
	// TableNamePackages is the name of the table containing the package model.
	TableNamePackages = "gophr.packages"
	// IndexNamePackages is the name of the lucene index
	IndexNamePackages             = "packages_index"
	ColumnNamePackagesRepo        = "repo"
	ColumnNamePackagesStars       = "stars"
	ColumnNamePackagesExists      = "exists"
	ColumnNamePackagesAuthor      = "author"
	ColumnNamePackagesVersions    = "versions"
	ColumnNamePackagesGodocURL    = "godoc_url"
	ColumnNamePackagesIndexTime   = "index_time"
	ColumnNamePackagesAwesomeGo   = "awesome_go"
	ColumnNamePackagesSearchBlob  = "search_blob"
	ColumnNamePackagesDescription = "description"
)

const (
	packagesSearchBlobTemplate = "%s %s %s"
)

var (
	cqlQueryFuzzySearchPackagesTemplate = fmt.Sprintf(
		`SELECT %s,%s,%s FROM %s WHERE expr(%s,'{query:{type:"fuzzy",field:"%s",value:"%s"}}') LIMIT 10`,
		ColumnNamePackagesRepo,
		ColumnNamePackagesAuthor,
		ColumnNamePackagesDescription,
		TableNamePackages,
		IndexNamePackages,
		ColumnNamePackagesSearchBlob,
		"%s",
	)

	cqlQuerySelectPackageVersions = fmt.Sprintf(
		`SELECT %s FROM %s WHERE %s = ? AND %s = ? LIMIT 1`,
		ColumnNamePackagesVersions,
		TableNamePackages,
		ColumnNamePackagesAuthor,
		ColumnNamePackagesRepo,
	)

	cqlQueryInsertPackage = fmt.Sprintf(
		`INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES (?,?,?,?,?,?,?,?,?,?)`,
		TableNamePackages,
		ColumnNamePackagesRepo,
		ColumnNamePackagesStars,
		ColumnNamePackagesExists,
		ColumnNamePackagesAuthor,
		ColumnNamePackagesVersions,
		ColumnNamePackagesGodocURL,
		ColumnNamePackagesIndexTime,
		ColumnNamePackagesAwesomeGo,
		ColumnNamePackagesSearchBlob,
		ColumnNamePackagesDescription,
	)
)

var (
	alphanumericFilterRegex = regexp.MustCompile(`[^\sa-zA-Z0-9\-_]+`)
)

// PackageModel is a struct representing one individual package in the database.
type PackageModel struct {
	Repo        *string
	Stars       *int
	Exists      *bool
	Author      *string
	Versions    []string
	GodocURL    *string
	IndexTime   *time.Time
	AwesomeGo   *bool
	SearchBlob  *string
	Description *string
}

// NewPackageModelForInsert creates an instance of PackageModel that is
// optimized and validated for the insert operation in the database.
func NewPackageModelForInsert(
	author string,
	exists bool,
	repo string,
	versions []string,
	godocURL string,
	indexTime time.Time,
	awesomeGo bool,
	description string,
	stars int,
) (*PackageModel, error) {
	if len(repo) < 1 {
		return nil, errors.NewInvalidParameterError("repo", repo)
	}
	if len(author) < 1 {
		return nil, errors.NewInvalidParameterError("author", author)
	}
	if len(godocURL) < 1 {
		return nil, errors.NewInvalidParameterError("godocURL", godocURL)
	}

	searchBlob := fmt.Sprintf(
		packagesSearchBlobTemplate,
		author,
		repo,
		description,
	)

	return &PackageModel{
		Repo:        &repo,
		Stars:       &stars,
		Exists:      &exists,
		Author:      &author,
		Versions:    versions,
		GodocURL:    &godocURL,
		IndexTime:   &indexTime,
		AwesomeGo:   &awesomeGo,
		SearchBlob:  &searchBlob,
		Description: &description,
	}, nil
}

// NewPackageModelFromBulkSelect creates an instance of PackageModel that is
// optimized and validated for a select operation designed to get data about
// multiple packages from the database.
func NewPackageModelFromBulkSelect(
	author string,
	repo string,
	description string,
) (*PackageModel, error) {
	if len(repo) < 1 {
		return nil, errors.NewInvalidParameterError("repo", repo)
	}
	if len(author) < 1 {
		return nil, errors.NewInvalidParameterError("author", author)
	}

	return &PackageModel{
		Repo:        &repo,
		Author:      &author,
		Description: &description,
	}, nil
}

func NewPackageModelTest(
	author string,
	repo string,
	awesome_go bool,
	description string,
	exists bool,
	godoc_url string,
	index_time time.Time,
	search_blob string,
	versions []string,
	stars int,
) *PackageModel {
	return &PackageModel{
		Repo:        &repo,
		Stars:       &stars,
		Exists:      &exists,
		Author:      &author,
		Versions:    versions,
		GodocURL:    &godoc_url,
		IndexTime:   &index_time,
		AwesomeGo:   &awesome_go,
		SearchBlob:  &search_blob,
		Description: &description,
	}
}

// NewPackageModelFromSingleSelect creates an instance of PackageModel that is
// optimized and validated for a select operation designed to get data about
// a single package from the database.
func NewPackageModelFromSingleSelect(
	author string,
	exists bool,
	repo string,
	versions []string,
	godocURL string,
	awesomeGo bool,
	description string,
) (*PackageModel, error) {
	if len(repo) < 1 {
		return nil, errors.NewInvalidParameterError("repo", repo)
	}
	if len(author) < 1 {
		return nil, errors.NewInvalidParameterError("author", author)
	}
	if len(godocURL) < 1 {
		return nil, errors.NewInvalidParameterError("godocURL", godocURL)
	}

	return &PackageModel{
		Repo:        &repo,
		Exists:      &exists,
		Author:      &author,
		Versions:    versions,
		GodocURL:    &godocURL,
		AwesomeGo:   &awesomeGo,
		Description: &description,
	}, nil
}

// FindPackageVersions gets the versions of a package from the database. If
// no such package exists, or there were no versions for said package, then nil
// is returned.
func FindPackageVersions(session *gocql.Session, author string, repo string) ([]string, error) {
	var (
		err      error
		versions []string
	)

	iter := session.Query(cqlQuerySelectPackageVersions, author, repo).Iter()

	if !iter.Scan(&versions) {
		return nil, nil
	}

	if err = iter.Close(); err != nil {
		return nil, errors.NewQueryScanError(nil, err)
	}

	return versions, nil
}

// FuzzySearchPackages finds a list of packages relevant to a query phrase
// string. The search takes author, package and description into account.
func FuzzySearchPackages(
	session *gocql.Session,
	searchText string,
) ([]*PackageModel, error) {
	// First, remove all non-essential characters
	searchText = alphanumericFilterRegex.ReplaceAllString(searchText, "")
	// Next put the search text into a query string
	query := fmt.Sprintf(cqlQueryFuzzySearchPackagesTemplate, searchText)
	// Return the processed results of the query
	return scanPackageModels(session.Query(query))
}

// InsertPackage inserts an individual package into the database.
func InsertPackage(
	session *gocql.Session,
	packageModel *PackageModel,
) error {
	err := session.Query(cqlQueryInsertPackage,
		*packageModel.Repo,
		*packageModel.Stars,
		*packageModel.Exists,
		*packageModel.Author,
		packageModel.Versions,
		*packageModel.GodocURL,
		*packageModel.IndexTime,
		*packageModel.AwesomeGo,
		*packageModel.SearchBlob,
		*packageModel.Description,
	).Exec()

	return err
}

// InsertPackages inserts a slice of package models into the database.
func InsertPackages(
	session *gocql.Session,
	packageModels []*PackageModel,
) error {
	batch := gocql.NewBatch(gocql.LoggedBatch)

	if packageModels == nil || len(packageModels) == 0 {
		return errors.NewInvalidParameterError("packageModels", packageModels)
	}

	for _, packageModel := range packageModels {
		if packageModel != nil &&
			packageModel.Repo != nil &&
			packageModel.Exists != nil &&
			packageModel.Author != nil &&
			packageModel.GodocURL != nil &&
			packageModel.IndexTime != nil &&
			packageModel.AwesomeGo != nil &&
			packageModel.SearchBlob != nil &&
			packageModel.Description != nil {
			batch.Query(
				cqlQueryInsertPackage,
				*packageModel.Repo,
				*packageModel.Exists,
				*packageModel.Author,
				packageModel.Versions,
				*packageModel.GodocURL,
				*packageModel.IndexTime,
				*packageModel.AwesomeGo,
				*packageModel.SearchBlob,
				*packageModel.Description,
			)
		} else {
			return errors.NewInvalidParameterError(
				"packageModels",
				fmt.Sprintf("[ ..., %v, ... ]", packageModel),
			)
		}
	}

	err := session.ExecuteBatch(batch)
	if err != nil {
		return err
	}

	return nil
}

/********************************** HELPERS ***********************************/

// TODO(skeswa): implement this for querying single packages
func scanPackageModel(query *gocql.Query) ([]*PackageModel, error) {
	return nil, nil
}

func scanPackageModels(query *gocql.Query) ([]*PackageModel, error) {
	var (
		err          error
		scanError    error
		closeError   error
		packageModel *PackageModel

		repo        string
		author      string
		description string

		iter          = query.Iter()
		packageModels = make([]*PackageModel, 0)
	)

	for iter.Scan(&repo, &author, &description) {
		packageModel, err = NewPackageModelFromBulkSelect(author, repo, description)
		if err != nil {
			scanError = err
			break
		} else {
			packageModels = append(packageModels, packageModel)
		}
	}

	if err = iter.Close(); err != nil {
		closeError = err
	}

	if scanError != nil || closeError != nil {
		return nil, errors.NewQueryScanError(scanError, closeError)
	}

	return packageModels, nil
}

func ScanAllPackageModels(session *gocql.Session) ([]*PackageModel, error) {
	var (
		err          error
		scanError    error
		closeError   error
		packageModel *PackageModel

		author      string
		repo        string
		awesome_go  bool
		description string
		exists      bool
		godoc_url   string
		index_time  time.Time
		search_blob string
		versions    []string
		stars       int

		query = session.Query(`SELECT
			author,
			repo,
			awesome_go,
			description,
			exists,
			godoc_url,
			index_time,
			search_blob,
			versions,
			stars
			FROM packages`)
		iter          = query.Iter()
		packageModels = make([]*PackageModel, 0)
	)

	for iter.Scan(&author, &repo, &awesome_go, &description, &exists, &godoc_url, &index_time, &search_blob, &versions, &stars) {
		packageModel = NewPackageModelTest(author, repo, awesome_go, description, exists, godoc_url, index_time, search_blob, versions, stars)
		packageModels = append(packageModels, packageModel)
	}

	if err = iter.Close(); err != nil {
		closeError = err
	}

	if scanError != nil || closeError != nil {
		return nil, errors.NewQueryScanError(scanError, closeError)
	}

	return packageModels, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/slack-go/slack"
)

const (
	MessageGroupDisplayTimestampFormat = "3:04pm"
	MessageTextBlockquotePrefix1       = ">"
	MessageTextBlockquotePrefix2       = ">>>"
	MessageTextControlRegexp           = "<(.*?)>"
	MessageTextEmojiRegexp             = ":([a-z0-9_\\-+]+):"
	MessageTextBoldRegexp              = "\\*([^*]+)\\*"
	MessageTextItalicRegexp            = "_([^_]+)_"
	MessageTextStrikethroughRegexp     = "~([^~]+)~"
	MessageTextInlineCodeRegexp        = "`([^`]+)`"
)

var controlRegexp *regexp.Regexp
var emojiRegexp *regexp.Regexp
var boldRegexp *regexp.Regexp
var italicRegexp *regexp.Regexp
var strikethroughRegexp *regexp.Regexp
var inlineCodeRegexp *regexp.Regexp

func init() {
	controlRegexp = regexp.MustCompile(MessageTextControlRegexp)
	emojiRegexp = regexp.MustCompile(MessageTextEmojiRegexp)
	boldRegexp = regexp.MustCompile(MessageTextBoldRegexp)
	italicRegexp = regexp.MustCompile(MessageTextItalicRegexp)
	strikethroughRegexp = regexp.MustCompile(MessageTextStrikethroughRegexp)
	inlineCodeRegexp = regexp.MustCompile(MessageTextInlineCodeRegexp)
}

func textToHtml(text string, truncate bool, slackClient *slack.Client) template.HTML {
	if truncate && len(text) > 700 {
		text = fmt.Sprintf("%s...", text[:700])
	}
	lines := strings.Split(text, "\n")
	if truncate && len(lines) > 5 {
		lines = append(lines[:5], "...")
	}
	htmlPieces := []string{}
	for i, line := range lines {
		linePrefix := ""
		lineSuffix := ""
		if strings.HasPrefix(line, MessageTextBlockquotePrefix1) ||
			strings.HasPrefix(line, MessageTextBlockquotePrefix2) {
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix1)
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix2)
			if line == "" {
				// Ensure that even empty blockquote lines get rendered.
				line = "\u200b"
			}
			linePrefix = fmt.Sprintf("
",
				Style("message.blockquote"))
			lineSuffix = "
"
		} else {
			if i != 0 {
				lineSuffix = "
"
			}
		}
		line = controlRegexp.ReplaceAllStringFunc(line, func(control string) string {
			control = control[1 : len(control)-1]
			anchorText := ""
			pipeIndex := strings.LastIndex(control, "|")
			if pipeIndex != -1 {
				anchorText = control[pipeIndex+1:]
				control = control[:pipeIndex]
			}
			if strings.HasPrefix(control, "@U") {
				userId := strings.TrimPrefix(control, "@")
				userLookup, err := newUserLookup(slackClient)
				if err == nil {
					user, err := userLookup.GetUser(userId)
					if err == nil {
						anchorText = fmt.Sprintf("@%s", user.Name)
						authTest, err := slackClient.AuthTest()
						if err == nil {
							control = fmt.Sprintf("%s/team/%s", authTest.URL, user.Name)
						} else {
							log.Printf("Could get team URL: %s", err)
						}
					} else {
						log.Printf("Could not render user mention: %s", err)
					}
				} else {
					log.Printf("Could not render user mention: %s", err)
				}
			} else if strings.HasPrefix(control, "#C") {
				channelId := strings.TrimPrefix(control, "#")
				channel, err := slackClient.GetConversationInfo(channelId, false)
				if err == nil {
					anchorText = fmt.Sprintf("#%s", channel.Name)
					authTest, err := slackClient.AuthTest()
					if err == nil {
						control = fmt.Sprintf("%s/team/%s", authTest.URL, channel.Name)
					} else {
						log.Printf("Could get team URL: %s", err)
					}
				} else {
					log.Printf("Could not render channel mention: %s", err)
				}
			} else if strings.HasPrefix(control, "!") {
				command := strings.TrimPrefix(control, "!")
				return fmt.Sprintf("@%s", command)
			}
			if anchorText == "" {
				anchorText = control
			}
			// Escape underscores to avoid triggering the italics regexp if
			// a filename has underscores in it.
			return fmt.Sprintf("%s",
				strings.Replace(control, "_", "%5F", -1),
				Style("message.link"),
				strings.Replace(anchorText, "_", "_", -1))
		})
		line = emojiRegexp.ReplaceAllStringFunc(line, func(emojiString string) string {
			shortName := emojiString[1 : len(emojiString)-1]
			if emojiHtml, err := getEmojiHtml(shortName, slackClient); err == nil {
				return fmt.Sprintf("%s", emojiString, emojiHtml)
			}
			return emojiString
		})
		line = boldRegexp.ReplaceAllString(line, "$1")
		line = italicRegexp.ReplaceAllString(line, "$1")
		line = strikethroughRegexp.ReplaceAllString(line, "$1")
		line = inlineCodeRegexp.ReplaceAllString(
			line,
			fmt.Sprintf("$1", Style("message.code")))

		htmlPieces = append(htmlPieces, linePrefix)
		// Slack's API claims that all HTML is already escaped
		htmlPieces = append(htmlPieces, line)
		htmlPieces = append(htmlPieces, lineSuffix)
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type Message struct {
	*slack.Message
	ReplyMessageGroups []*MessageGroup
	slackClient *slack.Client
	account     *Account
}

func (m *Message) TimestampTime() time.Time {
	floatTimestamp, err := strconv.ParseFloat(m.Timestamp, 64)
	if err != nil {
		log.Printf("Could not parse timestamp \"%s\": %s.\n", m.Timestamp, err)
		return time.Time{}
	}
	return time.Unix(int64(floatTimestamp), 0).In(m.account.TimezoneLocation)
}

func (m *Message) TextHtml() template.HTML {
	return textToHtml(m.Text, false, m.slackClient)
}

func (m *Message) StylePath() string {
	if strings.HasPrefix(m.SubType, "channel_") || strings.HasPrefix(m.SubType, "group_") {
		return "message.automated"
	}
	if m.SubType == "me_message" {
		return "message.me"
	}
	return ""
}

func (m *Message) MessageAttachments() []*MessageAttachment {
	attachments := make([]*MessageAttachment, 0, len(m.Attachments))
	for i := range m.Attachments {
		attachments = append(
			attachments, &MessageAttachment{&m.Attachments[i], m.slackClient})
	}
	return attachments
}

func (m *Message) MessageFile() *MessageFile {
	if len(m.Files) > 0 {
		return &MessageFile{&m.Files[0], m.slackClient, m.account}
	}
	return nil
}

func (m *Message) MessageReactions() []*MessageReaction {
	reactions := make([]*MessageReaction, 0, len(m.Reactions))
	for i := range m.Reactions {
		reactions = append(
			reactions, &MessageReaction{&m.Reactions[i], m.slackClient})
	}
	return reactions
}

func (m *Message) HasReplies() bool {
	// ReplyCount may be deprecated, don't rely on it.
	// https://api.slack.com/messaging/retrieving#finding_threads instructs to
	// rely on thread_ts and ts comparisons
	return (m.ThreadTimestamp != "" && m.Timestamp == m.ThreadTimestamp)  || m.ReplyCount > 0
}

type MessageAttachment struct {
	*slack.Attachment
	slackClient *slack.Client
}

func (a *MessageAttachment) BodyStyle() template.CSS {
	style := Style("message.attachment.body")
	if len(a.Color) > 0 {
		var color string
		if a.Color == "good" {
			color = "#2aa146"
		} else if a.Color == "warning" {
			color = "#de9e31"
		} else if a.Color == "danger" {
			color = "#d50200"
		} else {
			color = "#" + a.Color
		}
		style += template.CSS(fmt.Sprintf("border-color: %s", color))
	}
	return style
}

func (a *MessageAttachment) TitleHtml() template.HTML {
	return textToHtml(a.Title, false, a.slackClient)
}

func (a *MessageAttachment) PretextHtml() template.HTML {
	return textToHtml(a.Pretext, false, a.slackClient)
}

func (a *MessageAttachment) TextHtml() template.HTML {
	return textToHtml(a.Text, true, a.slackClient)
}

func (a *MessageAttachment) FieldsHtml() template.HTML {
	htmlPieces := []string{}
	inTable := false
	currentRowCellCount := 0
	for _, field := range a.Fields {
		fieldHtml := fmt.Sprintf(
			"
%s

%s
",
			Style("message.attachment.field.title"),
			textToHtml(field.Title, false, a.slackClient),
			textToHtml(field.Value, false, a.slackClient))
		if field.Short {
			if !inTable {
				htmlPieces = append(htmlPieces, fmt.Sprintf(
					"

",
					Style("message.attachment.field.table")))
				inTable = true
				currentRowCellCount = 0
			}
			if currentRowCellCount == 2 {
				htmlPieces = append(htmlPieces, "

")
				currentRowCellCount = 0
			}
			htmlPieces = append(htmlPieces, "")
			htmlPieces = append(htmlPieces, fieldHtml)
			htmlPieces = append(htmlPieces, "	")
			currentRowCellCount += 1
		} else {
			if inTable {
				htmlPieces = append(htmlPieces, "

")
				inTable = false
			}
			htmlPieces = append(htmlPieces, fieldHtml)
		}
	}
	if inTable {
		htmlPieces = append(htmlPieces, "

")
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type MessageFile struct {
	*slack.File
	slackClient *slack.Client
	account     *Account
}

func (f *MessageFile) ThumbnailUrl() (string, error) {
	if f.Thumb360 == "" {
		return "", nil
	}
	ref := FileUrlRef{f.ID, f.account.SlackUserId}
	encodedRef, err := ref.Encode()
	if err != nil {
		return "", err
	}
	return AbsoluteRouteUrl("archive-file-thumbnail", "ref", encodedRef)
}

func (f *MessageFile) ThumbnailWidth() int {
	return f.Thumb360W
}

func (f *MessageFile) ThumbnailHeight() int {
	return f.Thumb360H
}

func (f *MessageFile) PreviewHtml() template.HTML {
	if f.PreviewHighlight != "" {
		return template.HTML(f.PreviewHighlight)
	}
	return template.HTML(f.Preview)
}

type MessageReaction struct {
	*slack.ItemReaction
	slackClient *slack.Client
}

func (r *MessageReaction) Emoji() template.HTML {
	if emojiHtml, err := getEmojiHtml(r.Name, r.slackClient); err == nil {
		return template.HTML(emojiHtml)
	}
	return template.HTML(r.Name)
}

func (r *MessageReaction) Summary() (string, error) {
	userLookup, err := newUserLookup(r.slackClient)
	if err != nil {
		return "", err
	}
	names := make([]string, len(r.Users))
	for i := range r.Users {
		if user, err := userLookup.GetUser(r.Users[i]); err == nil {
			names[i] = user.Name
		} else {
			names[i] = r.Users[i]
		}
	}
	return fmt.Sprintf("%s reacted with :%s:",
		strings.Join(names, ", "), r.Name), nil
}

type MessageGroup struct {
	Messages []*Message
	Author   *slack.User
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func (mg *MessageGroup) shouldContainMessage(message *Message, messageAuthor *slack.User) bool {
	if messageAuthor.ID != mg.Author.ID {
		return false
	}
	lastMessage := mg.Messages[len(mg.Messages)-1]
	timestampDelta := message.TimestampTime().Sub(lastMessage.TimestampTime())
	if timestampDelta > time.Minute*10 {
		return false
	}
	return true
}

func (mg *MessageGroup) FromBot() bool {
	return mg.Messages[0].SubType == "bot_message"
}

func (mg *MessageGroup) DisplayTimestamp() string {
	return safeFormattedDate(mg.Messages[0].TimestampTime().Format(
		MessageGroupDisplayTimestampFormat))
}

func groupMessages(messages []*slack.Message, slackClient *slack.Client, account *Account) ([]*MessageGroup, error) {
	var currentGroup *MessageGroup = nil
	groups := make([]*MessageGroup, 0)
	userLookup, err := newUserLookup(slackClient)
	if err != nil {
		return nil, err
	}
	for i := range messages {
		message := &Message{messages[i], []*MessageGroup{}, slackClient, account}
		if message.Hidden {
			continue
		}
		messageAuthor, _ := userLookup.GetUserForMessage(messages[i])
		if messageAuthor == nil {
			log.Printf("Could not determine author for message type %s "+
				"(subtype %s), skipping", message.Type, message.SubType)
			continue
		}
		if currentGroup == nil || !currentGroup.shouldContainMessage(message, messageAuthor) {
			currentGroup = &MessageGroup{
				Messages: make([]*Message, 0),
				Author:   messageAuthor,
			}
			groups = append(groups, currentGroup)
		}
		currentGroup.Messages = append(currentGroup.Messages, message)
	}
	return groups, nil
}
<commit_msg>Fix channel and user mention URLs.<commit_after>package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/slack-go/slack"
)

const (
	MessageGroupDisplayTimestampFormat = "3:04pm"
	MessageTextBlockquotePrefix1       = ">"
	MessageTextBlockquotePrefix2       = ">>>"
	MessageTextControlRegexp           = "<(.*?)>"
	MessageTextEmojiRegexp             = ":([a-z0-9_\\-+]+):"
	MessageTextBoldRegexp              = "\\*([^*]+)\\*"
	MessageTextItalicRegexp            = "_([^_]+)_"
	MessageTextStrikethroughRegexp     = "~([^~]+)~"
	MessageTextInlineCodeRegexp        = "`([^`]+)`"
)

var controlRegexp *regexp.Regexp
var emojiRegexp *regexp.Regexp
var boldRegexp *regexp.Regexp
var italicRegexp *regexp.Regexp
var strikethroughRegexp *regexp.Regexp
var inlineCodeRegexp *regexp.Regexp

func init() {
	controlRegexp = regexp.MustCompile(MessageTextControlRegexp)
	emojiRegexp = regexp.MustCompile(MessageTextEmojiRegexp)
	boldRegexp = regexp.MustCompile(MessageTextBoldRegexp)
	italicRegexp = regexp.MustCompile(MessageTextItalicRegexp)
	strikethroughRegexp = regexp.MustCompile(MessageTextStrikethroughRegexp)
	inlineCodeRegexp = regexp.MustCompile(MessageTextInlineCodeRegexp)
}

func textToHtml(text string, truncate bool, slackClient *slack.Client) template.HTML {
	if truncate && len(text) > 700 {
		text = fmt.Sprintf("%s...", text[:700])
	}
	lines := strings.Split(text, "\n")
	if truncate && len(lines) > 5 {
		lines = append(lines[:5], "...")
	}
	htmlPieces := []string{}
	for i, line := range lines {
		linePrefix := ""
		lineSuffix := ""
		if strings.HasPrefix(line, MessageTextBlockquotePrefix1) ||
			strings.HasPrefix(line, MessageTextBlockquotePrefix2) {
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix1)
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix2)
			if line == "" {
				// Ensure that even empty blockquote lines get rendered.
				line = "\u200b"
			}
			linePrefix = fmt.Sprintf("
",
				Style("message.blockquote"))
			lineSuffix = "
"
		} else {
			if i != 0 {
				lineSuffix = "
"
			}
		}
		line = controlRegexp.ReplaceAllStringFunc(line, func(control string) string {
			control = control[1 : len(control)-1]
			anchorText := ""
			pipeIndex := strings.LastIndex(control, "|")
			if pipeIndex != -1 {
				anchorText = control[pipeIndex+1:]
				control = control[:pipeIndex]
			}
			if strings.HasPrefix(control, "@U") {
				userId := strings.TrimPrefix(control, "@")
				userLookup, err := newUserLookup(slackClient)
				if err == nil {
					user, err := userLookup.GetUser(userId)
					if err == nil {
						anchorText = fmt.Sprintf("@%s", user.Name)
						control = fmt.Sprintf("https://slack.com/app_redirect?team=%s&channel=%s", user.TeamID, userId)
					} else {
						log.Printf("Could not render user mention: %s", err)
					}
				} else {
					log.Printf("Could not render user mention: %s", err)
				}
			} else if strings.HasPrefix(control, "#C") {
				channelId := strings.TrimPrefix(control, "#")
				channel, err := slackClient.GetConversationInfo(channelId, false)
				if err == nil {
					anchorText = fmt.Sprintf("#%s", channel.Name)
					control = fmt.Sprintf("https://slack.com/app_redirect?channel=%s", channelId)
				} else {
					log.Printf("Could not render channel mention: %s", err)
				}
			} else if strings.HasPrefix(control, "!") {
				command := strings.TrimPrefix(control, "!")
				return fmt.Sprintf("@%s", command)
			}
			if anchorText == "" {
				anchorText = control
			}
			// Escape underscores to avoid triggering the italics regexp if
			// a filename has underscores in it.
			return fmt.Sprintf("%s",
				strings.Replace(control, "_", "%5F", -1),
				Style("message.link"),
				strings.Replace(anchorText, "_", "_", -1))
		})
		line = emojiRegexp.ReplaceAllStringFunc(line, func(emojiString string) string {
			shortName := emojiString[1 : len(emojiString)-1]
			if emojiHtml, err := getEmojiHtml(shortName, slackClient); err == nil {
				return fmt.Sprintf("%s", emojiString, emojiHtml)
			}
			return emojiString
		})
		line = boldRegexp.ReplaceAllString(line, "$1")
		line = italicRegexp.ReplaceAllString(line, "$1")
		line = strikethroughRegexp.ReplaceAllString(line, "$1")
		line = inlineCodeRegexp.ReplaceAllString(
			line,
			fmt.Sprintf("$1", Style("message.code")))

		htmlPieces = append(htmlPieces, linePrefix)
		// Slack's API claims that all HTML is already escaped
		htmlPieces = append(htmlPieces, line)
		htmlPieces = append(htmlPieces, lineSuffix)
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type Message struct {
	*slack.Message
	ReplyMessageGroups []*MessageGroup
	slackClient *slack.Client
	account     *Account
}

func (m *Message) TimestampTime() time.Time {
	floatTimestamp, err := strconv.ParseFloat(m.Timestamp, 64)
	if err != nil {
		log.Printf("Could not parse timestamp \"%s\": %s.\n", m.Timestamp, err)
		return time.Time{}
	}
	return time.Unix(int64(floatTimestamp), 0).In(m.account.TimezoneLocation)
}

func (m *Message) TextHtml() template.HTML {
	return textToHtml(m.Text, false, m.slackClient)
}

func (m *Message) StylePath() string {
	if strings.HasPrefix(m.SubType, "channel_") || strings.HasPrefix(m.SubType, "group_") {
		return "message.automated"
	}
	if m.SubType == "me_message" {
		return "message.me"
	}
	return ""
}

func (m *Message) MessageAttachments() []*MessageAttachment {
	attachments := make([]*MessageAttachment, 0, len(m.Attachments))
	for i := range m.Attachments {
		attachments = append(
			attachments, &MessageAttachment{&m.Attachments[i], m.slackClient})
	}
	return attachments
}

func (m *Message) MessageFile() *MessageFile {
	if len(m.Files) > 0 {
		return &MessageFile{&m.Files[0], m.slackClient, m.account}
	}
	return nil
}

func (m *Message) MessageReactions() []*MessageReaction {
	reactions := make([]*MessageReaction, 0, len(m.Reactions))
	for i := range m.Reactions {
		reactions = append(
			reactions, &MessageReaction{&m.Reactions[i], m.slackClient})
	}
	return reactions
}

func (m *Message) HasReplies() bool {
	// ReplyCount may be deprecated, don't rely on it.
	// https://api.slack.com/messaging/retrieving#finding_threads instructs to
	// rely on thread_ts and ts comparisons
	return (m.ThreadTimestamp != "" && m.Timestamp == m.ThreadTimestamp)  || m.ReplyCount > 0
}

type MessageAttachment struct {
	*slack.Attachment
	slackClient *slack.Client
}

func (a *MessageAttachment) BodyStyle() template.CSS {
	style := Style("message.attachment.body")
	if len(a.Color) > 0 {
		var color string
		if a.Color == "good" {
			color = "#2aa146"
		} else if a.Color == "warning" {
			color = "#de9e31"
		} else if a.Color == "danger" {
			color = "#d50200"
		} else {
			color = "#" + a.Color
		}
		style += template.CSS(fmt.Sprintf("border-color: %s", color))
	}
	return style
}

func (a *MessageAttachment) TitleHtml() template.HTML {
	return textToHtml(a.Title, false, a.slackClient)
}

func (a *MessageAttachment) PretextHtml() template.HTML {
	return textToHtml(a.Pretext, false, a.slackClient)
}

func (a *MessageAttachment) TextHtml() template.HTML {
	return textToHtml(a.Text, true, a.slackClient)
}

func (a *MessageAttachment) FieldsHtml() template.HTML {
	htmlPieces := []string{}
	inTable := false
	currentRowCellCount := 0
	for _, field := range a.Fields {
		fieldHtml := fmt.Sprintf(
			"
%s

%s
",
			Style("message.attachment.field.title"),
			textToHtml(field.Title, false, a.slackClient),
			textToHtml(field.Value, false, a.slackClient))
		if field.Short {
			if !inTable {
				htmlPieces = append(htmlPieces, fmt.Sprintf(
					"

",
					Style("message.attachment.field.table")))
				inTable = true
				currentRowCellCount = 0
			}
			if currentRowCellCount == 2 {
				htmlPieces = append(htmlPieces, "

")
				currentRowCellCount = 0
			}
			htmlPieces = append(htmlPieces, "")
			htmlPieces = append(htmlPieces, fieldHtml)
			htmlPieces = append(htmlPieces, "	")
			currentRowCellCount += 1
		} else {
			if inTable {
				htmlPieces = append(htmlPieces, "

")
				inTable = false
			}
			htmlPieces = append(htmlPieces, fieldHtml)
		}
	}
	if inTable {
		htmlPieces = append(htmlPieces, "

")
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type MessageFile struct {
	*slack.File
	slackClient *slack.Client
	account     *Account
}

func (f *MessageFile) ThumbnailUrl() (string, error) {
	if f.Thumb360 == "" {
		return "", nil
	}
	ref := FileUrlRef{f.ID, f.account.SlackUserId}
	encodedRef, err := ref.Encode()
	if err != nil {
		return "", err
	}
	return AbsoluteRouteUrl("archive-file-thumbnail", "ref", encodedRef)
}

func (f *MessageFile) ThumbnailWidth() int {
	return f.Thumb360W
}

func (f *MessageFile) ThumbnailHeight() int {
	return f.Thumb360H
}

func (f *MessageFile) PreviewHtml() template.HTML {
	if f.PreviewHighlight != "" {
		return template.HTML(f.PreviewHighlight)
	}
	return template.HTML(f.Preview)
}

type MessageReaction struct {
	*slack.ItemReaction
	slackClient *slack.Client
}

func (r *MessageReaction) Emoji() template.HTML {
	if emojiHtml, err := getEmojiHtml(r.Name, r.slackClient); err == nil {
		return template.HTML(emojiHtml)
	}
	return template.HTML(r.Name)
}

func (r *MessageReaction) Summary() (string, error) {
	userLookup, err := newUserLookup(r.slackClient)
	if err != nil {
		return "", err
	}
	names := make([]string, len(r.Users))
	for i := range r.Users {
		if user, err := userLookup.GetUser(r.Users[i]); err == nil {
			names[i] = user.Name
		} else {
			names[i] = r.Users[i]
		}
	}
	return fmt.Sprintf("%s reacted with :%s:",
		strings.Join(names, ", "), r.Name), nil
}

type MessageGroup struct {
	Messages []*Message
	Author   *slack.User
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func (mg *MessageGroup) shouldContainMessage(message *Message, messageAuthor *slack.User) bool {
	if messageAuthor.ID != mg.Author.ID {
		return false
	}
	lastMessage := mg.Messages[len(mg.Messages)-1]
	timestampDelta := message.TimestampTime().Sub(lastMessage.TimestampTime())
	if timestampDelta > time.Minute*10 {
		return false
	}
	return true
}

func (mg *MessageGroup) FromBot() bool {
	return mg.Messages[0].SubType == "bot_message"
}

func (mg *MessageGroup) DisplayTimestamp() string {
	return safeFormattedDate(mg.Messages[0].TimestampTime().Format(
		MessageGroupDisplayTimestampFormat))
}

func groupMessages(messages []*slack.Message, slackClient *slack.Client, account *Account) ([]*MessageGroup, error) {
	var currentGroup *MessageGroup = nil
	groups := make([]*MessageGroup, 0)
	userLookup, err := newUserLookup(slackClient)
	if err != nil {
		return nil, err
	}
	for i := range messages {
		message := &Message{messages[i], []*MessageGroup{}, slackClient, account}
		if message.Hidden {
			continue
		}
		messageAuthor, _ := userLookup.GetUserForMessage(messages[i])
		if messageAuthor == nil {
			log.Printf("Could not determine author for message type %s "+
				"(subtype %s), skipping", message.Type, message.SubType)
			continue
		}
		if currentGroup == nil || !currentGroup.shouldContainMessage(message, messageAuthor) {
			currentGroup = &MessageGroup{
				Messages: make([]*Message, 0),
				Author:   messageAuthor,
			}
			groups = append(groups, currentGroup)
		}
		currentGroup.Messages = append(currentGroup.Messages, message)
	}
	return groups, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSEcrDataSource_ecrRepository(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsEcrRepositoryDataSourceConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr("data.aws_ecr_repository.default", "arn", regexp.MustCompile("^arn:aws:ecr:[a-zA-Z]{2}-[a-zA-Z]{4}-\\d:\\d+:repository/foo-repository-terraform-\\d+$")),
					resource.TestCheckResourceAttrSet("data.aws_ecr_repository.default", "registry_id"),
					resource.TestMatchResourceAttr("data.aws_ecr_repository.default", "repository_url", regexp.MustCompile("^\\d+\\.dkr\\.ecr\\.[a-zA-Z]{2}-[a-zA-Z]{4}-\\d\\.amazonaws\\.com/foo-repository-terraform-\\d+$")),
				),
			},
		},
	})
}

var testAccCheckAwsEcrRepositoryDataSourceConfig = fmt.Sprintf(`
resource "aws_ecr_repository" "default" {
  name = "foo-repository-terraform-%d"
}

data "aws_ecr_repository" "default" {
  name = "${aws_ecr_repository.default.name}"
}
`, acctest.RandInt())
<commit_msg>Allow regions other than eu-west-1 format<commit_after>package aws

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSEcrDataSource_ecrRepository(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccCheckAwsEcrRepositoryDataSourceConfig,
				Check: resource.ComposeTestCheckFunc(
					resource.TestMatchResourceAttr("data.aws_ecr_repository.default", "arn", regexp.MustCompile("^arn:aws:ecr:[a-zA-Z]+-[a-zA-Z]+-\\d+:\\d+:repository/foo-repository-terraform-\\d+$")),
					resource.TestCheckResourceAttrSet("data.aws_ecr_repository.default", "registry_id"),
					resource.TestMatchResourceAttr("data.aws_ecr_repository.default", "repository_url", regexp.MustCompile("^\\d+\\.dkr\\.ecr\\.[a-zA-Z]+-[a-zA-Z]+-\\d+\\.amazonaws\\.com/foo-repository-terraform-\\d+$")),
				),
			},
		},
	})
}

var testAccCheckAwsEcrRepositoryDataSourceConfig = fmt.Sprintf(`
resource "aws_ecr_repository" "default" {
  name = "foo-repository-terraform-%d"
}

data "aws_ecr_repository" "default" {
  name = "${aws_ecr_repository.default.name}"
}
`, acctest.RandInt())
<|endoftext|>


<commit_before>package aws

import (
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIAMServerCertificate() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIAMServerCertificateCreate,
		Read:   resourceAwsIAMServerCertificateRead,
		Delete: resourceAwsIAMServerCertificateDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsIAMServerCertificateImport,
		},

		Schema: map[string]*schema.Schema{
			"certificate_body": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"certificate_chain": {
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"path": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
				ForceNew: true,
			},

			"private_key": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
				Sensitive: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 128 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 128 characters", k))
					}
					return
				},
			},

			"name_prefix": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 102 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 102 characters, name is limited to 128", k))
					}
					return
				},
			},

			"arn": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourceAwsIAMServerCertificateCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn

	var sslCertName string
	if v, ok := d.GetOk("name"); ok {
		sslCertName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		sslCertName = resource.PrefixedUniqueId(v.(string))
	} else {
		sslCertName = resource.UniqueId()
	}

	createOpts := &iam.UploadServerCertificateInput{
		CertificateBody:       aws.String(d.Get("certificate_body").(string)),
		PrivateKey:            aws.String(d.Get("private_key").(string)),
		ServerCertificateName: aws.String(sslCertName),
	}

	if v, ok := d.GetOk("certificate_chain"); ok {
		createOpts.CertificateChain = aws.String(v.(string))
	}

	if v, ok := d.GetOk("path"); ok {
		createOpts.Path = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating IAM Server Certificate with opts: %s", createOpts)
	resp, err := conn.UploadServerCertificate(createOpts)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			return fmt.Errorf("[WARN] Error uploading server certificate, error: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error uploading server certificate, error: %s", err)
	}

	d.SetId(*resp.ServerCertificateMetadata.ServerCertificateId)
	d.Set("name", sslCertName)

	return resourceAwsIAMServerCertificateRead(d, meta)
}

func resourceAwsIAMServerCertificateRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	resp, err := conn.GetServerCertificate(&iam.GetServerCertificateInput{
		ServerCertificateName: aws.String(d.Get("name").(string)),
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "NoSuchEntity" {
				log.Printf("[WARN] IAM Server Cert (%s) not found, removing from state", d.Id())
				d.SetId("")
				return nil
			}
			return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err)
	}

	d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId)

	// these values should always be present, and have a default if not set in
	// configuration, and so safe to reference with nil checks
	d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody))

	c := normalizeCert(resp.ServerCertificate.CertificateChain)
	if c != "" {
		d.Set("certificate_chain", c)
	}

	d.Set("path", resp.ServerCertificate.ServerCertificateMetadata.Path)
	d.Set("arn", resp.ServerCertificate.ServerCertificateMetadata.Arn)

	return nil
}

func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	log.Printf("[INFO] Deleting IAM Server Certificate: %s", d.Id())
	err := resource.Retry(15*time.Minute, func() *resource.RetryError {
		_, err := conn.DeleteServerCertificate(&iam.DeleteServerCertificateInput{
			ServerCertificateName: aws.String(d.Get("name").(string)),
		})

		if err != nil {
			if awsErr, ok := err.(awserr.Error); ok {
				if awsErr.Code() == "DeleteConflict" && strings.Contains(awsErr.Message(), "currently in use by arn") {
					log.Printf("[WARN] Conflict deleting server certificate: %s, retrying", awsErr.Message())
					return resource.RetryableError(err)
				}
				if awsErr.Code() == "NoSuchEntity" {
					log.Printf("[WARN] IAM Server Certificate (%s) not found, removing from state", d.Id())
					d.SetId("")
					return nil
				}
			}
			return resource.NonRetryableError(err)
		}
		return nil
	})

	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}

func resourceAwsIAMServerCertificateImport(
	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	d.Set("name", d.Id())
	// private_key can't be fetched from any API call
	return []*schema.ResourceData{d}, nil
}

func normalizeCert(cert interface{}) string {
	if cert == nil || cert == (*string)(nil) {
		return ""
	}

	var rawCert string
	switch cert.(type) {
	case string:
		rawCert = cert.(string)
	case *string:
		rawCert = *cert.(*string)
	default:
		return ""
	}

	cleanVal := sha1.Sum(stripCR([]byte(strings.TrimSpace(rawCert))))
	return hex.EncodeToString(cleanVal[:])
}

// strip CRs from raw literals. Lifted from go/scanner/scanner.go
// See https://github.com/golang/go/blob/release-branch.go1.6/src/go/scanner/scanner.go#L479
func stripCR(b []byte) []byte {
	c := make([]byte, len(b))
	i := 0
	for _, ch := range b {
		if ch != '\r' {
			c[i] = ch
			i++
		}
	}
	return c[:i]
}
<commit_msg>Add logs for iam server certificate delete conflict (#2533)<commit_after>package aws

import (
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIAMServerCertificate() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIAMServerCertificateCreate,
		Read:   resourceAwsIAMServerCertificateRead,
		Delete: resourceAwsIAMServerCertificateDelete,
		Importer: &schema.ResourceImporter{
			State: resourceAwsIAMServerCertificateImport,
		},

		Schema: map[string]*schema.Schema{
			"certificate_body": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"certificate_chain": {
				Type:      schema.TypeString,
				Optional:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
			},

			"path": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
				ForceNew: true,
			},

			"private_key": {
				Type:      schema.TypeString,
				Required:  true,
				ForceNew:  true,
				StateFunc: normalizeCert,
				Sensitive: true,
			},

			"name": {
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 128 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 128 characters", k))
					}
					return
				},
			},

			"name_prefix": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if len(value) > 102 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 102 characters, name is limited to 128", k))
					}
					return
				},
			},

			"arn": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourceAwsIAMServerCertificateCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn

	var sslCertName string
	if v, ok := d.GetOk("name"); ok {
		sslCertName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		sslCertName = resource.PrefixedUniqueId(v.(string))
	} else {
		sslCertName = resource.UniqueId()
	}

	createOpts := &iam.UploadServerCertificateInput{
		CertificateBody:       aws.String(d.Get("certificate_body").(string)),
		PrivateKey:            aws.String(d.Get("private_key").(string)),
		ServerCertificateName: aws.String(sslCertName),
	}

	if v, ok := d.GetOk("certificate_chain"); ok {
		createOpts.CertificateChain = aws.String(v.(string))
	}

	if v, ok := d.GetOk("path"); ok {
		createOpts.Path = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating IAM Server Certificate with opts: %s", createOpts)
	resp, err := conn.UploadServerCertificate(createOpts)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			return fmt.Errorf("[WARN] Error uploading server certificate, error: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error uploading server certificate, error: %s", err)
	}

	d.SetId(*resp.ServerCertificateMetadata.ServerCertificateId)
	d.Set("name", sslCertName)

	return resourceAwsIAMServerCertificateRead(d, meta)
}

func resourceAwsIAMServerCertificateRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	resp, err := conn.GetServerCertificate(&iam.GetServerCertificateInput{
		ServerCertificateName: aws.String(d.Get("name").(string)),
	})

	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "NoSuchEntity" {
				log.Printf("[WARN] IAM Server Cert (%s) not found, removing from state", d.Id())
				d.SetId("")
				return nil
			}
			return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s: %s", awsErr.Code(), awsErr.Message())
		}
		return fmt.Errorf("[WARN] Error reading IAM Server Certificate: %s", err)
	}

	d.SetId(*resp.ServerCertificate.ServerCertificateMetadata.ServerCertificateId)

	// these values should always be present, and have a default if not set in
	// configuration, and so safe to reference with nil checks
	d.Set("certificate_body", normalizeCert(resp.ServerCertificate.CertificateBody))

	c := normalizeCert(resp.ServerCertificate.CertificateChain)
	if c != "" {
		d.Set("certificate_chain", c)
	}

	d.Set("path", resp.ServerCertificate.ServerCertificateMetadata.Path)
	d.Set("arn", resp.ServerCertificate.ServerCertificateMetadata.Arn)

	return nil
}

func resourceAwsIAMServerCertificateDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).iamconn
	log.Printf("[INFO] Deleting IAM Server Certificate: %s", d.Id())
	err := resource.Retry(15*time.Minute, func() *resource.RetryError {
		_, err := conn.DeleteServerCertificate(&iam.DeleteServerCertificateInput{
			ServerCertificateName: aws.String(d.Get("name").(string)),
		})

		if err != nil {
			if awsErr, ok := err.(awserr.Error); ok {
				if awsErr.Code() == "DeleteConflict" && strings.Contains(awsErr.Message(), "currently in use by arn") {
					currentlyInUseBy(awsErr.Message(), meta.(*AWSClient).elbconn)
					log.Printf("[WARN] Conflict deleting server certificate: %s, retrying", awsErr.Message())
					return resource.RetryableError(err)
				}
				if awsErr.Code() == "NoSuchEntity" {
					log.Printf("[WARN] IAM Server Certificate (%s) not found, removing from state", d.Id())
					d.SetId("")
					return nil
				}
			}
			return resource.NonRetryableError(err)
		}
		return nil
	})

	if err != nil {
		return err
	}

	d.SetId("")
	return nil
}

func resourceAwsIAMServerCertificateImport(
	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
	d.Set("name", d.Id())
	// private_key can't be fetched from any API call
	return []*schema.ResourceData{d}, nil
}

func currentlyInUseBy(awsErr string, conn *elb.ELB) {
	r := regexp.MustCompile(`currently in use by ([a-z0-9:-]+)\/([a-z0-9-]+)\.`)
	matches := r.FindStringSubmatch(awsErr)
	if len(matches) > 0 {
		lbName := matches[2]
		describeElbOpts := &elb.DescribeLoadBalancersInput{
			LoadBalancerNames: []*string{aws.String(lbName)},
		}
		if _, err := conn.DescribeLoadBalancers(describeElbOpts); err != nil {
			if isAWSErr(err, "LoadBalancerNotFound", "") {
				log.Printf("[WARN] Load Balancer (%s) causing delete conflict not found", lbName)
			}
		}
	}
}

func normalizeCert(cert interface{}) string {
	if cert == nil || cert == (*string)(nil) {
		return ""
	}

	var rawCert string
	switch cert.(type) {
	case string:
		rawCert = cert.(string)
	case *string:
		rawCert = *cert.(*string)
	default:
		return ""
	}

	cleanVal := sha1.Sum(stripCR([]byte(strings.TrimSpace(rawCert))))
	return hex.EncodeToString(cleanVal[:])
}

// strip CRs from raw literals. Lifted from go/scanner/scanner.go
// See https://github.com/golang/go/blob/release-branch.go1.6/src/go/scanner/scanner.go#L479
func stripCR(b []byte) []byte {
	c := make([]byte, len(b))
	i := 0
	for _, ch := range b {
		if ch != '\r' {
			c[i] = ch
			i++
		}
	}
	return c[:i]
}
<|endoftext|>


<commit_before>package main

import (
	"os/user"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/intelsdi-x/swan/pkg/conf"
	"github.com/intelsdi-x/swan/pkg/executor"
	"github.com/intelsdi-x/swan/pkg/experiment/sensitivity"
	"github.com/intelsdi-x/swan/pkg/isolation"
	"github.com/intelsdi-x/swan/pkg/kubernetes"
	"github.com/intelsdi-x/swan/pkg/snap"
	"github.com/intelsdi-x/swan/pkg/snap/sessions/mutilate"
	"github.com/intelsdi-x/swan/pkg/utils/errutil"
	"github.com/intelsdi-x/swan/pkg/workloads/memcached"
	"github.com/intelsdi-x/swan/pkg/workloads/mutilate"
)

var (
	// Aggressors flag.
	aggressorsFlag = conf.NewSliceFlag(
		"aggr", "Aggressor to run experiment with. You can state as many as you want (--aggr=l1d --aggr=membw)")

	// Mutilate configuration.
	percentileFlag     = conf.NewStringFlag("percentile", "Tail latency Percentile", "99")
	mutilateMasterFlag = conf.NewIPFlag(
		"mutilate_master",
		"Mutilate master host for remote executor. In case of 0 agents being specified it runs in agentless mode.",
		"127.0.0.1")
	mutilateAgentsFlag = conf.NewSliceFlag(
		"mutilate_agent",
		"Mutilate agent hosts for remote executor. Can be specified many times for multiple agents setup.")
	runOnKubernetesFlag = conf.NewBoolFlag("run_on_kubernetes", "Launch HP and BE tasks on Kubernetes.", false)

	mutilateMasterFlagDefault = "local"
)

// newRemote is helper for creating remotes with default sshConfig.
func newRemote(ip string) executor.Executor {
	// TODO(bp): Have ability to choose user using parameter here.
	user, err := user.Current()
	errutil.Check(err)

	sshConfig, err := executor.NewSSHConfig(ip, executor.DefaultSSHPort, user)
	errutil.Check(err)

	return executor.NewRemote(sshConfig)
}

func prepareSnapSessionLauncher() snap.SessionLauncher {
	var mutilateSnapSession snap.SessionLauncher

	// NOTE: For debug it is convenient to disable snap for some experiment runs.
	if snap.SnapdHTTPEndpoint.Value() != "none" {
		// Create connection with Snap.
		logrus.Info("Connecting to Snapd on ", snap.SnapdHTTPEndpoint.Value())
		// TODO(bp): Make helper for passing host:port or only host option here.

		mutilateConfig := mutilatesession.DefaultConfig()
		mutilateConfig.SnapdAddress = snap.SnapdHTTPEndpoint.Value()
		mutilateSnapSession, err := mutilatesession.NewSessionLauncher(mutilateConfig)
		errutil.Check(err)
		return mutilateSnapSession
	}
	return mutilateSnapSession
}

func isManualPolicy() bool {
	return hpSetsFlag.Value() != "" && beSetsFlag.Value() != ""
}

// DecoratorFunc is a dummy decorator as a workaround for a isolation for inside a docker.
type DecoratorFunc func(string) string

// Decorate wrap method for a command.
func (df DecoratorFunc) Decorate(command string) string {
	return df(command)
}

// Check README.md for details of this experiment.
func main() {
	// Setup conf.
	conf.SetAppName("memcached-sensitivity-profile")
	conf.SetHelp(`Sensitivity experiment runs different measurements to test the performance of co-located workloads on a single node.
It executes workloads and triggers gathering of certain metrics like latency (SLI) and the achieved number of Request per Second (QPS/RPS)`)

	// Parse CLI.
	errutil.Check(conf.ParseFlags())

	logrus.SetLevel(conf.LogLevel())

	// Validate environment.
	validateOS()

	// Isolation configuration method.
	// TODO: needs update for different isolation per cpu
	var hpIsolation, beIsolation, l1Isolation, llcIsolation isolation.Isolation
	var aggressorFactory sensitivity.AggressorFactory

	if runOnKubernetesFlag.Value() {
		var dummyIsolation DecoratorFunc = func(s string) string { return s }
		aggressorFactory = sensitivity.NewSingleIsolationAggressorFactory(dummyIsolation)
	} else if isManualPolicy() {
	 	hpIsolation, beIsolation = manualPolicy()
	 	aggressorFactory = sensitivity.NewSingleIsolationAggressorFactory(beIsolation)
	 	defer beIsolation.Clean()
		defer hpIsolation.Clean()
	} else {
	 	// NOTE: Temporary hack for having multiple isolations in Sensitivity Profile.
	 	hpIsolation, l1Isolation, llcIsolation = sensitivityProfileIsolationPolicy()
	 	aggressorFactory = sensitivity.NewMultiIsolationAggressorFactory(l1Isolation, llcIsolation)
	 	defer l1Isolation.Clean()
	 	defer llcIsolation.Clean()
		defer hpIsolation.Clean()
	}

	var hpExecutor executor.Executor
	var err error
	var memcachedLauncher memcached.Memcached
	memcachedConfig := memcached.DefaultMemcachedConfig()
	mutilateConfig := mutilate.DefaultMutilateConfig()

	if runOnKubernetesFlag.Value() {
		k8sConfig, err := kubernetes.DefaultConfig()
		errutil.Check(err)
		k8sLauncher := kubernetes.New(executor.NewLocal(), executor.NewLocal(), k8sConfig)
		k8sClusterTaskHandle, err := k8sLauncher.Launch()
		defer executor.StopCleanAndErase(k8sClusterTaskHandle)

		hpExecutorConfig := executor.DefaultKubernetesConfig()
		hpExecutorConfig.ContainerImage = "centos_swan_image"
		hpExecutorConfig.PodName = "swan-hp"
		hpExecutor, err = executor.NewKubernetes(hpExecutorConfig)
		errutil.Check(err)
	} else {
		hpExecutor = executor.NewLocalIsolated(hpIsolation)
	}

	memcachedLauncher = memcached.New(hpExecutor, memcachedConfig) // Initialize Memcached Launcher.

	mutilateConfig.MemcachedHost = memcachedConfig.IP
	mutilateConfig.MemcachedPort = memcachedConfig.Port
	mutilateConfig.LatencyPercentile = percentileFlag.Value()
	mutilateConfig.TuningTime = 1 * time.Second

	// Special case to have ability to use local executor for mutilate master load generator.
	// This is needed for docker testing.
	var masterLoadGeneratorExecutor executor.Executor
	masterLoadGeneratorExecutor = executor.NewLocal()
	if mutilateMasterFlag.Value() != mutilateMasterFlagDefault {
		masterLoadGeneratorExecutor = newRemote(mutilateMasterFlag.Value())
	}

	// Pack agents.
	agentsLoadGeneratorExecutors := []executor.Executor{}
	for _, agent := range mutilateAgentsFlag.Value() {
		agentsLoadGeneratorExecutors = append(agentsLoadGeneratorExecutors, newRemote(agent))
	}
	logrus.Debugf("Added %d mutilate agent(s) to mutilate cluster", len(agentsLoadGeneratorExecutors))

	// Validate mutilate cluster executors and their limit of
	// number of open file descriptors. Sane mutilate configuration requires
	// more than default (1024) for mutilate cluster.
	validateExecutorsNOFILELimit(
		append(agentsLoadGeneratorExecutors, masterLoadGeneratorExecutor),
	)

	// Initialize Mutilate Load Generator.
	mutilateLoadGenerator := mutilate.NewCluster(
		masterLoadGeneratorExecutor,
		agentsLoadGeneratorExecutors,
		mutilateConfig)

	// Initialize aggressors with BE isolation.
	aggressors := []sensitivity.LauncherSessionPair{}
	for _, aggressorName := range aggressorsFlag.Value() {
		aggressor, err := aggressorFactory.Create(aggressorName, runOnKubernetesFlag.Value())
		errutil.Check(err)

		aggressors = append(aggressors, sensitivity.NewLauncherWithoutSession(aggressor))
	}

	// Snap Session for mutilate.
	mutilateSnapSession := prepareSnapSessionLauncher()

	// Create Experiment configuration from Conf.
	sensitivityExperiment := sensitivity.NewExperiment(
		conf.AppName(),
		conf.LogLevel(),
		sensitivity.DefaultConfiguration(),
		sensitivity.NewLauncherWithoutSession(memcachedLauncher),
		sensitivity.NewMonitoredLoadGenerator(mutilateLoadGenerator, mutilateSnapSession),
		aggressors,
	)

	// Run Experiment.
	err = sensitivityExperiment.Run()
	errutil.Check(err)
}
<commit_msg>k8s: Default config is not return error anymore<commit_after>package main

import (
	"os/user"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/intelsdi-x/swan/pkg/conf"
	"github.com/intelsdi-x/swan/pkg/executor"
	"github.com/intelsdi-x/swan/pkg/experiment/sensitivity"
	"github.com/intelsdi-x/swan/pkg/isolation"
	"github.com/intelsdi-x/swan/pkg/kubernetes"
	"github.com/intelsdi-x/swan/pkg/snap"
	"github.com/intelsdi-x/swan/pkg/snap/sessions/mutilate"
	"github.com/intelsdi-x/swan/pkg/utils/errutil"
	"github.com/intelsdi-x/swan/pkg/workloads/memcached"
	"github.com/intelsdi-x/swan/pkg/workloads/mutilate"
)

var (
	// Aggressors flag.
	aggressorsFlag = conf.NewSliceFlag(
		"aggr", "Aggressor to run experiment with. You can state as many as you want (--aggr=l1d --aggr=membw)")

	// Mutilate configuration.
	percentileFlag     = conf.NewStringFlag("percentile", "Tail latency Percentile", "99")
	mutilateMasterFlag = conf.NewIPFlag(
		"mutilate_master",
		"Mutilate master host for remote executor. In case of 0 agents being specified it runs in agentless mode.",
		"127.0.0.1")
	mutilateAgentsFlag = conf.NewSliceFlag(
		"mutilate_agent",
		"Mutilate agent hosts for remote executor. Can be specified many times for multiple agents setup.")
	runOnKubernetesFlag = conf.NewBoolFlag("run_on_kubernetes", "Launch HP and BE tasks on Kubernetes.", false)

	mutilateMasterFlagDefault = "local"
)

// newRemote is helper for creating remotes with default sshConfig.
func newRemote(ip string) executor.Executor {
	// TODO(bp): Have ability to choose user using parameter here.
	user, err := user.Current()
	errutil.Check(err)

	sshConfig, err := executor.NewSSHConfig(ip, executor.DefaultSSHPort, user)
	errutil.Check(err)

	return executor.NewRemote(sshConfig)
}

func prepareSnapSessionLauncher() snap.SessionLauncher {
	var mutilateSnapSession snap.SessionLauncher

	// NOTE: For debug it is convenient to disable snap for some experiment runs.
	if snap.SnapdHTTPEndpoint.Value() != "none" {
		// Create connection with Snap.
		logrus.Info("Connecting to Snapd on ", snap.SnapdHTTPEndpoint.Value())
		// TODO(bp): Make helper for passing host:port or only host option here.

		mutilateConfig := mutilatesession.DefaultConfig()
		mutilateConfig.SnapdAddress = snap.SnapdHTTPEndpoint.Value()
		mutilateSnapSession, err := mutilatesession.NewSessionLauncher(mutilateConfig)
		errutil.Check(err)
		return mutilateSnapSession
	}
	return mutilateSnapSession
}

func isManualPolicy() bool {
	return hpSetsFlag.Value() != "" && beSetsFlag.Value() != ""
}

// DecoratorFunc is a dummy decorator as a workaround for a isolation for inside a docker.
type DecoratorFunc func(string) string

// Decorate wrap method for a command.
func (df DecoratorFunc) Decorate(command string) string {
	return df(command)
}

// Check README.md for details of this experiment.
func main() {
	// Setup conf.
	conf.SetAppName("memcached-sensitivity-profile")
	conf.SetHelp(`Sensitivity experiment runs different measurements to test the performance of co-located workloads on a single node.
It executes workloads and triggers gathering of certain metrics like latency (SLI) and the achieved number of Request per Second (QPS/RPS)`)

	// Parse CLI.
	errutil.Check(conf.ParseFlags())

	logrus.SetLevel(conf.LogLevel())

	// Validate environment.
	validateOS()

	// Isolation configuration method.
	// TODO: needs update for different isolation per cpu
	var hpIsolation, beIsolation, l1Isolation, llcIsolation isolation.Isolation
	var aggressorFactory sensitivity.AggressorFactory

	if runOnKubernetesFlag.Value() {
		var dummyIsolation DecoratorFunc = func(s string) string { return s }
		aggressorFactory = sensitivity.NewSingleIsolationAggressorFactory(dummyIsolation)
	} else if isManualPolicy() {
	 	hpIsolation, beIsolation = manualPolicy()
	 	aggressorFactory = sensitivity.NewSingleIsolationAggressorFactory(beIsolation)
	 	defer beIsolation.Clean()
		defer hpIsolation.Clean()
	} else {
	 	// NOTE: Temporary hack for having multiple isolations in Sensitivity Profile.
	 	hpIsolation, l1Isolation, llcIsolation = sensitivityProfileIsolationPolicy()
	 	aggressorFactory = sensitivity.NewMultiIsolationAggressorFactory(l1Isolation, llcIsolation)
	 	defer l1Isolation.Clean()
	 	defer llcIsolation.Clean()
		defer hpIsolation.Clean()
	}

	var hpExecutor executor.Executor
	var err error
	var memcachedLauncher memcached.Memcached
	memcachedConfig := memcached.DefaultMemcachedConfig()
	mutilateConfig := mutilate.DefaultMutilateConfig()

	if runOnKubernetesFlag.Value() {
		k8sConfig := kubernetes.DefaultConfig()
		k8sLauncher := kubernetes.New(executor.NewLocal(), executor.NewLocal(), k8sConfig)
		k8sClusterTaskHandle, err := k8sLauncher.Launch()
		errutil.Check(err)
		defer executor.StopCleanAndErase(k8sClusterTaskHandle)

		hpExecutorConfig := executor.DefaultKubernetesConfig()
		hpExecutorConfig.ContainerImage = "centos_swan_image"
		hpExecutorConfig.PodName = "swan-hp"
		hpExecutor, err = executor.NewKubernetes(hpExecutorConfig)
		errutil.Check(err)
	} else {
		hpExecutor = executor.NewLocalIsolated(hpIsolation)
	}

	memcachedLauncher = memcached.New(hpExecutor, memcachedConfig) // Initialize Memcached Launcher.

	mutilateConfig.MemcachedHost = memcachedConfig.IP
	mutilateConfig.MemcachedPort = memcachedConfig.Port
	mutilateConfig.LatencyPercentile = percentileFlag.Value()
	mutilateConfig.TuningTime = 1 * time.Second

	// Special case to have ability to use local executor for mutilate master load generator.
	// This is needed for docker testing.
	var masterLoadGeneratorExecutor executor.Executor
	masterLoadGeneratorExecutor = executor.NewLocal()
	if mutilateMasterFlag.Value() != mutilateMasterFlagDefault {
		masterLoadGeneratorExecutor = newRemote(mutilateMasterFlag.Value())
	}

	// Pack agents.
	agentsLoadGeneratorExecutors := []executor.Executor{}
	for _, agent := range mutilateAgentsFlag.Value() {
		agentsLoadGeneratorExecutors = append(agentsLoadGeneratorExecutors, newRemote(agent))
	}
	logrus.Debugf("Added %d mutilate agent(s) to mutilate cluster", len(agentsLoadGeneratorExecutors))

	// Validate mutilate cluster executors and their limit of
	// number of open file descriptors. Sane mutilate configuration requires
	// more than default (1024) for mutilate cluster.
	validateExecutorsNOFILELimit(
		append(agentsLoadGeneratorExecutors, masterLoadGeneratorExecutor),
	)

	// Initialize Mutilate Load Generator.
	mutilateLoadGenerator := mutilate.NewCluster(
		masterLoadGeneratorExecutor,
		agentsLoadGeneratorExecutors,
		mutilateConfig)

	// Initialize aggressors with BE isolation.
	aggressors := []sensitivity.LauncherSessionPair{}
	for _, aggressorName := range aggressorsFlag.Value() {
		aggressor, err := aggressorFactory.Create(aggressorName, runOnKubernetesFlag.Value())
		errutil.Check(err)

		aggressors = append(aggressors, sensitivity.NewLauncherWithoutSession(aggressor))
	}

	// Snap Session for mutilate.
	mutilateSnapSession := prepareSnapSessionLauncher()

	// Create Experiment configuration from Conf.
	sensitivityExperiment := sensitivity.NewExperiment(
		conf.AppName(),
		conf.LogLevel(),
		sensitivity.DefaultConfiguration(),
		sensitivity.NewLauncherWithoutSession(memcachedLauncher),
		sensitivity.NewMonitoredLoadGenerator(mutilateLoadGenerator, mutilateSnapSession),
		aggressors,
	)

	// Run Experiment.
	err = sensitivityExperiment.Run()
	errutil.Check(err)
}
<|endoftext|>


<commit_before>package medeina

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

var standard http.Handler

func init() {
	standard = loadStandard()
}

func loadStandard() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/events/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/v1/events/" {
			http.NotFound(w, r)
			return
		}
		fmt.Fprint(w, "This is the home page")
	})
	mux.HandleFunc("/api/v1/events/list", list)
	mr := NewMedeina()
	mr.OnHandler("api/v1/events", mux)
	return mr
}

func list(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "This is a list")
}

func TestEmbeddedNegroni(t *testing.T) {
	testRequests(t, "GET", "/api/v1/events/", http.StatusOK)
	testRequests(t, "GET", "/api/v1/events/xlistx", http.StatusNotFound)
	testRequests(t, "GET", "/api/v1/events/list", http.StatusOK)
}

func testRequests(t *testing.T, method, path string, expectedStatus int) {
	r, _ := http.NewRequest(method, path, nil)
	w := new(httptest.ResponseRecorder)
	u := r.URL
	r.RequestURI = u.RequestURI()
	standard.ServeHTTP(w, r)
	if w.Code != expectedStatus {
		t.Errorf("Expected %d for route %s %s found: Code=%d", expectedStatus, method, u, w.Code)
		panic(t)
	}
}
<commit_msg>Renaming test TestEmbeddedNegroni.<commit_after>package medeina

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

var standard http.Handler

func init() {
	standard = loadStandard()
}

func loadStandard() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/events/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/v1/events/" {
			http.NotFound(w, r)
			return
		}
		fmt.Fprint(w, "This is the home page")
	})
	mux.HandleFunc("/api/v1/events/list", list)
	mr := NewMedeina()
	mr.OnHandler("api/v1/events", mux)
	return mr
}

func list(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "This is a list")
}

func TestHandlers(t *testing.T) {
	testRequests(t, "GET", "/api/v1/events/", http.StatusOK)
	testRequests(t, "GET", "/api/v1/events/xlistx", http.StatusNotFound)
	testRequests(t, "GET", "/api/v1/events/list", http.StatusOK)
}

func testRequests(t *testing.T, method, path string, expectedStatus int) {
	r, _ := http.NewRequest(method, path, nil)
	w := new(httptest.ResponseRecorder)
	u := r.URL
	r.RequestURI = u.RequestURI()
	standard.ServeHTTP(w, r)
	if w.Code != expectedStatus {
		t.Errorf("Expected %d for route %s %s found: Code=%d", expectedStatus, method, u, w.Code)
		panic(t)
	}
}
<|endoftext|>


<commit_before>package webdriver_test

import (
	. "github.com/sclevine/agouti/page/internal/webdriver"

	"errors"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/sclevine/agouti/internal/mocks"
	"github.com/sclevine/agouti/page/internal/webdriver/element"
	"github.com/sclevine/agouti/page/internal/webdriver/window"
)

var _ = Describe("Webdriver", func() {
	var (
		driver  *Driver
		session *mocks.Session
		err     error
	)

	BeforeEach(func() {
		session = &mocks.Session{}
		driver = &Driver{session}
	})

	Describe("#Navigate", func() {
		BeforeEach(func() {
			err = driver.Navigate("http://example.com")
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("POST"))
		})

		It("hits the /url endpoint", func() {
			Expect(session.Endpoint).To(Equal("url"))
		})

		It("includes the new URL in the request body", func() {
			Expect(session.BodyJSON).To(MatchJSON(`{"url": "http://example.com"}`))
		})

		Context("when the sesssion indicates a success", func() {
			It("doesn't return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to navigate", func() {
				session.Err = errors.New("some error")
				err = driver.Navigate("http://example.com")
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#GetElements", func() {
		var elements []Element

		BeforeEach(func() {
			session.Result = `[{"ELEMENT": "some-id"}, {"ELEMENT": "some-other-id"}]`
			elements, err = driver.GetElements("#selector")
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("POST"))
		})

		It("hits the /url endpoint", func() {
			Expect(session.Endpoint).To(Equal("elements"))
		})

		It("includes the new URL in the request body", func() {
			Expect(session.BodyJSON).To(MatchJSON(`{"using": "css selector", "value": "#selector"}`))
		})

		Context("when the session indicates a success", func() {
			It("returns a slice of elements with IDs and sessions", func() {
				Expect(elements[0].(*element.Element).ID).To(Equal("some-id"))
				Expect(elements[0].(*element.Element).Session).To(Equal(session))
				Expect(elements[1].(*element.Element).ID).To(Equal("some-other-id"))
				Expect(elements[1].(*element.Element).Session).To(Equal(session))
			})

			It("does not return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the session failed to retrieve the elements", func() {
				session.Err = errors.New("some error")
				_, err = driver.GetElements("#selector")
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#GetWindow", func() {
		var driverWindow Window

		BeforeEach(func() {
			session.Result = `"some-id"`
			driverWindow, err = driver.GetWindow()
		})

		It("makes a GET request", func() {
			Expect(session.Method).To(Equal("GET"))
		})

		It("hits the /window_handle endpoint", func() {
			Expect(session.Endpoint).To(Equal("window_handle"))
		})

		Context("when the session indicates a success", func() {
			It("returns the window with the retrieved ID and session", func() {
				Expect(driverWindow.(*window.Window).ID).To(Equal("some-id"))
				Expect(driverWindow.(*window.Window).Session).To(Equal(session))
			})

			It("does not return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the session failed to retrieve the elements", func() {
				session.Err = errors.New("some error")
				_, err = driver.GetWindow()
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#SetCookie", func() {
		var cookie *Cookie

		BeforeEach(func() {
			cookie = &Cookie{
				Name:     "some-name",
				Value:    42,
				Path:     "/my-path",
				Domain:   "example.com",
				Secure:   false,
				HTTPOnly: false,
				Expiry:   1412358590,
			}

			err = driver.SetCookie(cookie)
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("POST"))
		})

		It("hits the /cookie endpoint", func() {
			Expect(session.Endpoint).To(Equal("cookie"))
		})

		It("includes the cookie to add in the request body", func() {
			Expect(session.BodyJSON).To(MatchJSON(`{"cookie":{"name":"some-name","value":42,"path":"/my-path","domain":"example.com","secure":false,"httpOnly":false,"expiry":1412358590}}`))
		})

		Context("when the sesssion indicates a success", func() {
			It("does not return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to add the cookie", func() {
				session.Err = errors.New("some error")
				err = driver.SetCookie(cookie)
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#DeleteCookie", func() {
		BeforeEach(func() {
			err = driver.DeleteCookie("some-cookie")
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("DELETE"))
		})

		It("hits the /cookie/:name endpoint", func() {
			Expect(session.Endpoint).To(Equal("cookie/some-cookie"))
		})

		Context("when the sesssion indicates a success", func() {
			It("doesn't return an error", func() {
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to delete the cookie", func() {
				session.Err = errors.New("some error")
				err = driver.DeleteCookie("some-cookie")
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#DeleteAllCookies", func() {
		BeforeEach(func() {
			err = driver.DeleteAllCookies()
		})

		It("makes a DELETE request", func() {
			Expect(session.Method).To(Equal("DELETE"))
		})

		It("hits the /cookie endpoint", func() {
			Expect(session.Endpoint).To(Equal("cookie"))
		})

		Context("when the sesssion indicates a success", func() {
			It("doesn't return an error", func() {
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to delete the cookies", func() {
				session.Err = errors.New("some error")
				err = driver.DeleteAllCookies()
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#Screenshot", func() {
		var (
			image []byte
			err   error
		)

		BeforeEach(func() {
			session.Result = `"c29tZS1wbmc="`
			image, err = driver.Screenshot()
		})

		It("makes a GET request", func() {
			Expect(session.Method).To(Equal("GET"))
		})

		It("hits the /screenshot endpoint", func() {
			Expect(session.Endpoint).To(Equal("screenshot"))
		})

		Context("when the session indicates a success", func() {
			Context("when the image is valid base64", func() {
				It("returns the decoded image", func() {
					Expect(string(image)).To(Equal("some-png"))
				})

				It("does not return an error", func() {
					Expect(err).NotTo(HaveOccurred())
				})
			})

			Context("and the image is not valid base64", func() {
				BeforeEach(func() {
					session.Result = `"..."`
					image, err = driver.Screenshot()
				})

				It("returns an error", func() {
					Expect(err).To(Equal(""))
				})
			})
		})

		Context("when the session indicates a failure", func() {
			BeforeEach(func() {
				session.Err = errors.New("some error")
				image, err = driver.Screenshot()
			})

			It("returns an error", func() {
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#GetURL", func() {
		var url string

		BeforeEach(func() {
			session.Result = `"http://example.com"`
			url, err = driver.GetURL()
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("GET"))
		})

		It("hits the /url endpoint", func() {
			Expect(session.Endpoint).To(Equal("url"))
		})

		Context("when the sesssion indicates a success", func() {
			It("returns the page URL", func() {
				Expect(url).To(Equal("http://example.com"))
			})

			It("doesn't return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to add the cookie", func() {
				session.Err = errors.New("some error")
				_, err = driver.GetURL()
				Expect(err).To(MatchError("some error"))
			})
		})
	})
})
<commit_msg>Replace empty error in base64 encoding test<commit_after>package webdriver_test

import (
	. "github.com/sclevine/agouti/page/internal/webdriver"

	"errors"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/sclevine/agouti/internal/mocks"
	"github.com/sclevine/agouti/page/internal/webdriver/element"
	"github.com/sclevine/agouti/page/internal/webdriver/window"
)

var _ = Describe("Webdriver", func() {
	var (
		driver  *Driver
		session *mocks.Session
		err     error
	)

	BeforeEach(func() {
		session = &mocks.Session{}
		driver = &Driver{session}
	})

	Describe("#Navigate", func() {
		BeforeEach(func() {
			err = driver.Navigate("http://example.com")
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("POST"))
		})

		It("hits the /url endpoint", func() {
			Expect(session.Endpoint).To(Equal("url"))
		})

		It("includes the new URL in the request body", func() {
			Expect(session.BodyJSON).To(MatchJSON(`{"url": "http://example.com"}`))
		})

		Context("when the sesssion indicates a success", func() {
			It("doesn't return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to navigate", func() {
				session.Err = errors.New("some error")
				err = driver.Navigate("http://example.com")
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#GetElements", func() {
		var elements []Element

		BeforeEach(func() {
			session.Result = `[{"ELEMENT": "some-id"}, {"ELEMENT": "some-other-id"}]`
			elements, err = driver.GetElements("#selector")
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("POST"))
		})

		It("hits the /url endpoint", func() {
			Expect(session.Endpoint).To(Equal("elements"))
		})

		It("includes the new URL in the request body", func() {
			Expect(session.BodyJSON).To(MatchJSON(`{"using": "css selector", "value": "#selector"}`))
		})

		Context("when the session indicates a success", func() {
			It("returns a slice of elements with IDs and sessions", func() {
				Expect(elements[0].(*element.Element).ID).To(Equal("some-id"))
				Expect(elements[0].(*element.Element).Session).To(Equal(session))
				Expect(elements[1].(*element.Element).ID).To(Equal("some-other-id"))
				Expect(elements[1].(*element.Element).Session).To(Equal(session))
			})

			It("does not return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the session failed to retrieve the elements", func() {
				session.Err = errors.New("some error")
				_, err = driver.GetElements("#selector")
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#GetWindow", func() {
		var driverWindow Window

		BeforeEach(func() {
			session.Result = `"some-id"`
			driverWindow, err = driver.GetWindow()
		})

		It("makes a GET request", func() {
			Expect(session.Method).To(Equal("GET"))
		})

		It("hits the /window_handle endpoint", func() {
			Expect(session.Endpoint).To(Equal("window_handle"))
		})

		Context("when the session indicates a success", func() {
			It("returns the window with the retrieved ID and session", func() {
				Expect(driverWindow.(*window.Window).ID).To(Equal("some-id"))
				Expect(driverWindow.(*window.Window).Session).To(Equal(session))
			})

			It("does not return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the session failed to retrieve the elements", func() {
				session.Err = errors.New("some error")
				_, err = driver.GetWindow()
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#SetCookie", func() {
		var cookie *Cookie

		BeforeEach(func() {
			cookie = &Cookie{
				Name:     "some-name",
				Value:    42,
				Path:     "/my-path",
				Domain:   "example.com",
				Secure:   false,
				HTTPOnly: false,
				Expiry:   1412358590,
			}

			err = driver.SetCookie(cookie)
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("POST"))
		})

		It("hits the /cookie endpoint", func() {
			Expect(session.Endpoint).To(Equal("cookie"))
		})

		It("includes the cookie to add in the request body", func() {
			Expect(session.BodyJSON).To(MatchJSON(`{"cookie":{"name":"some-name","value":42,"path":"/my-path","domain":"example.com","secure":false,"httpOnly":false,"expiry":1412358590}}`))
		})

		Context("when the sesssion indicates a success", func() {
			It("does not return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to add the cookie", func() {
				session.Err = errors.New("some error")
				err = driver.SetCookie(cookie)
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#DeleteCookie", func() {
		BeforeEach(func() {
			err = driver.DeleteCookie("some-cookie")
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("DELETE"))
		})

		It("hits the /cookie/:name endpoint", func() {
			Expect(session.Endpoint).To(Equal("cookie/some-cookie"))
		})

		Context("when the sesssion indicates a success", func() {
			It("doesn't return an error", func() {
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to delete the cookie", func() {
				session.Err = errors.New("some error")
				err = driver.DeleteCookie("some-cookie")
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#DeleteAllCookies", func() {
		BeforeEach(func() {
			err = driver.DeleteAllCookies()
		})

		It("makes a DELETE request", func() {
			Expect(session.Method).To(Equal("DELETE"))
		})

		It("hits the /cookie endpoint", func() {
			Expect(session.Endpoint).To(Equal("cookie"))
		})

		Context("when the sesssion indicates a success", func() {
			It("doesn't return an error", func() {
				Expect(err).ToNot(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to delete the cookies", func() {
				session.Err = errors.New("some error")
				err = driver.DeleteAllCookies()
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#Screenshot", func() {
		var (
			image []byte
			err   error
		)

		BeforeEach(func() {
			session.Result = `"c29tZS1wbmc="`
			image, err = driver.Screenshot()
		})

		It("makes a GET request", func() {
			Expect(session.Method).To(Equal("GET"))
		})

		It("hits the /screenshot endpoint", func() {
			Expect(session.Endpoint).To(Equal("screenshot"))
		})

		Context("when the session indicates a success", func() {
			Context("when the image is valid base64", func() {
				It("returns the decoded image", func() {
					Expect(string(image)).To(Equal("some-png"))
				})

				It("does not return an error", func() {
					Expect(err).NotTo(HaveOccurred())
				})
			})

			Context("and the image is not valid base64", func() {
				BeforeEach(func() {
					session.Result = `"..."`
					image, err = driver.Screenshot()
				})

				It("returns an error", func() {
					Expect(err).To(MatchError("illegal base64 data at input byte 0"))
				})
			})
		})

		Context("when the session indicates a failure", func() {
			BeforeEach(func() {
				session.Err = errors.New("some error")
				image, err = driver.Screenshot()
			})

			It("returns an error", func() {
				Expect(err).To(MatchError("some error"))
			})
		})
	})

	Describe("#GetURL", func() {
		var url string

		BeforeEach(func() {
			session.Result = `"http://example.com"`
			url, err = driver.GetURL()
		})

		It("makes a POST request", func() {
			Expect(session.Method).To(Equal("GET"))
		})

		It("hits the /url endpoint", func() {
			Expect(session.Endpoint).To(Equal("url"))
		})

		Context("when the sesssion indicates a success", func() {
			It("returns the page URL", func() {
				Expect(url).To(Equal("http://example.com"))
			})

			It("doesn't return an error", func() {
				Expect(err).NotTo(HaveOccurred())
			})
		})

		Context("when the session indicates a failure", func() {
			It("returns an error indicating the page failed to add the cookie", func() {
				session.Err = errors.New("some error")
				_, err = driver.GetURL()
				Expect(err).To(MatchError("some error"))
			})
		})
	})
})
<|endoftext|>