<commit_before>package models

import (
	"errors"
	"time"

	"github.com/koding/bongo"
)

type ChannelMessage struct {
	// unique identifier of the channel message
	Id int64 `json:"id"`

	// Body of the mesage
	Body string `json:"body"`

	// Generated Slug for body
	Slug string `json:"slug" 			           sql:"NOT NULL;UNIQUE"`

	// type of the message
	Type string `json:"type"                        sql:"NOT NULL"`

	// Creator of the channel message
	AccountId int64 `json:"accountId"               sql:"NOT NULL"`

	// in which channel this message is created
	InitialChannelId int64 `json:"initialChannelId" sql:"NOT NULL"`

	// Creation date of the message
	CreatedAt time.Time `json:"createdAt"           sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Modification date of the message
	UpdatedAt time.Time `json:"updatedAt"           sql:"DEFAULT:CURRENT_TIMESTAMP"`
}

func (c *ChannelMessage) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *ChannelMessage) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c *ChannelMessage) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c *ChannelMessage) GetId() int64 {
	return c.Id
}

func (c *ChannelMessage) TableName() string {
	return "channel_message"
}

const (
	ChannelMessage_TYPE_POST  = "post"
	ChannelMessage_TYPE_REPLY = "reply"
	ChannelMessage_TYPE_JOIN  = "join"
	ChannelMessage_TYPE_LEAVE = "leave"
	ChannelMessage_TYPE_CHAT  = "chat"
)

func NewChannelMessage() *ChannelMessage {
	return &ChannelMessage{}
}

func (c *ChannelMessage) Fetch() error {
	return bongo.B.Fetch(c)
}

func (c *ChannelMessage) Update() error {
	// only update body
	err := bongo.B.UpdatePartial(c,
		map[string]interface{}{
			"body": c.Body,
		},
	)
	return err
}

func (c *ChannelMessage) Create() error {
	var err error
	c, err = Slugify(c)
	if err != nil {
		return err
	}

	return bongo.B.Create(c)
}

func (c *ChannelMessage) Delete() error {
	return bongo.B.Delete(c)
}

func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

	if len(ids) == 0 {
		return messages, nil
	}

	if err := bongo.B.FetchByIds(c, &messages, ids); err != nil {
		return nil, err
	}
	return messages, nil
}

func (c *ChannelMessage) FetchRelatives() (*ChannelMessageContainer, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel message id is not set")
	}
	container := NewChannelMessageContainer()
	container.Message = c

	i := NewInteraction()
	i.MessageId = c.Id

	interactions, err := i.List("like")
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.Actors = interactions
	// check this from database
	interactionContainer.IsInteracted = true

	if container.Interactions == nil {
		container.Interactions = make(map[string]*InteractionContainer)
	}
	if _, ok := container.Interactions["like"]; !ok {
		container.Interactions["like"] = NewInteractionContainer()
	}
	container.Interactions["like"] = interactionContainer
	return container, nil
}
<commit_msg>Social: add sql tags  for ChannelMessage<commit_after>package models

import (
	"errors"
	"time"

	"github.com/koding/bongo"
)

type ChannelMessage struct {
	// unique identifier of the channel message
	Id int64 `json:"id"`

	// Body of the mesage
	Body string `json:"body"`

	// Generated Slug for body
	Slug string `json:"slug"                       sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// type of the message
	TypeConstant string `json:"typeConstant"        sql:"NOT NULL"`

	// Creator of the channel message
	AccountId int64 `json:"accountId"               sql:"NOT NULL"`

	// in which channel this message is created
	InitialChannelId int64 `json:"initialChannelId" sql:"NOT NULL"`

	// Creation date of the message
	CreatedAt time.Time `json:"createdAt"           sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Modification date of the message
	UpdatedAt time.Time `json:"updatedAt"           sql:"DEFAULT:CURRENT_TIMESTAMP"`
}

func (c *ChannelMessage) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *ChannelMessage) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c *ChannelMessage) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c *ChannelMessage) GetId() int64 {
	return c.Id
}

func (c *ChannelMessage) TableName() string {
	return "channel_message"
}

const (
	ChannelMessage_TYPE_POST  = "post"
	ChannelMessage_TYPE_REPLY = "reply"
	ChannelMessage_TYPE_JOIN  = "join"
	ChannelMessage_TYPE_LEAVE = "leave"
	ChannelMessage_TYPE_CHAT  = "chat"
)

func NewChannelMessage() *ChannelMessage {
	return &ChannelMessage{}
}

func (c *ChannelMessage) Fetch() error {
	return bongo.B.Fetch(c)
}

func (c *ChannelMessage) Update() error {
	// only update body
	err := bongo.B.UpdatePartial(c,
		map[string]interface{}{
			"body": c.Body,
		},
	)
	return err
}

func (c *ChannelMessage) Create() error {
	var err error
	c, err = Slugify(c)
	if err != nil {
		return err
	}

	return bongo.B.Create(c)
}

func (c *ChannelMessage) Delete() error {
	return bongo.B.Delete(c)
}

func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

	if len(ids) == 0 {
		return messages, nil
	}

	if err := bongo.B.FetchByIds(c, &messages, ids); err != nil {
		return nil, err
	}
	return messages, nil
}

func (c *ChannelMessage) FetchRelatives() (*ChannelMessageContainer, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel message id is not set")
	}
	container := NewChannelMessageContainer()
	container.Message = c

	i := NewInteraction()
	i.MessageId = c.Id

	interactions, err := i.List("like")
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.Actors = interactions
	// check this from database
	interactionContainer.IsInteracted = true

	if container.Interactions == nil {
		container.Interactions = make(map[string]*InteractionContainer)
	}
	if _, ok := container.Interactions["like"]; !ok {
		container.Interactions["like"] = NewInteractionContainer()
	}
	container.Interactions["like"] = interactionContainer
	return container, nil
}
<|endoftext|>


<commit_before>package models

import (
	"encoding/json"
	"koding/db/mongodb/modelhelper"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
)

const (
	ChannelRequestMessage_TYPE_INVITE = "invite"
	ChannelRequestMessage_TYPE_JOIN   = "join"
	ChannelRequestMessage_TYPE_LEAVE  = "leave"
	ChannelRequestMessage_TYPE_REJECT = "reject"
	ChannelRequestMessage_TYPE_KICK   = "kick"
	ChannelRequestMessage_TYPE_INIT   = "initiate"
)

type ChannelRequest struct {
	Body            string      `json:"body"`
	Name            string      `json:"name"`
	Payload         gorm.Hstore `json:"payload,omitempty"`
	GroupName       string      `json:"groupName"`
	Recipients      []string
	AccountId       int64  `json:"accountId,string"`
	ChannelId       int64  `json:"channelId,string"`
	RequestData     string `json:"requestData"`
	ClientRequestId string `json:"ClientRequestId"`
	Purpose         string `json:"purpose"`
	TypeConstant    string `json:"type"`
}

func (p *ChannelRequest) Create() (*ChannelContainer, error) {
	// validate the request
	if err := p.validate(); err != nil {
		return nil, err
	}

	// fetch participants
	participantIds, err := p.obtainParticipantIds()
	if err != nil {
		return nil, err
	}

	// create the channel
	c := NewChannel()
	c.GroupName = p.GroupName
	c.CreatorId = p.AccountId
	c.Name = RandomName()
	c.TypeConstant = p.TypeConstant
	c.Purpose = p.Purpose
	c.Payload = p.Payload

	if p.Name != "" {
		c.Name = p.Name
	}

	// all topic channels under koding, should be public
	if c.TypeConstant == Channel_TYPE_TOPIC && c.GroupName == Channel_KODING_NAME {
		c.PrivacyConstant = Channel_PRIVACY_PUBLIC
	}

	if err := c.Create(); err != nil {
		return nil, err
	}

	groupChannel, err := Cache.Channel.ByGroupName(c.GroupName)
	if err != nil {
		return nil, err
	}

	// add participants to the channel
	for _, participantId := range participantIds {
		// users should be in regarding group channel
		if err := checkForGroupParticipation(groupChannel, participantId); err != nil {
			return nil, err
		}

		cp, err := c.AddParticipant(participantId)
		if err != nil {
			continue
		}

		if participantId != p.AccountId {
			continue
		}

		// do not show unread count to the creator of the private message
		oneSecondLater := time.Now().UTC().Add(time.Second * 1)
		// do not show unread count as 1 to user
		err = cp.RawUpdateLastSeenAt(oneSecondLater)
		if err != nil {
			return nil, err
		}
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE
	if p.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	return p.buildInitContainer(c, participantIds, typeConstant)
}

func checkForGroupParticipation(groupChannel *Channel, participantId int64) error {
	// everyone is a member of koding group
	if groupChannel.GroupName == Channel_KODING_NAME {
		return nil
	}

	isParticipant, err := groupChannel.IsParticipant(participantId)
	if err != nil {
		return nil
	}

	if !isParticipant {
		return ErrCannotOpenChannel
	}

	return nil
}

func (p *ChannelRequest) buildInitContainer(c *Channel, participantIds []int64, typeConstant string) (*ChannelContainer, error) {

	np := &ChannelRequest{}
	*np = *p
	lastMessage, err := p.AddInitActivity(c, participantIds)
	if err != nil {
		return nil, err
	}

	if np.Body != "" {
		var err error
		// create private message

		lastMessage, err = p.createActivity(c, typeConstant)
		if err != nil {
			return nil, err
		}
	}

	cmc, err := p.buildContainer(c, lastMessage)
	if err != nil {
		return nil, err
	}

	participantOldIds, err := FetchAccountOldsIdByIdsFromCache(participantIds)
	if err != nil {
		return nil, err
	}

	// set participant count
	cmc.ParticipantCount = len(participantIds)
	// set preview
	cmc.ParticipantsPreview = participantOldIds

	return cmc, nil
}

func (p *ChannelRequest) Send() (*ChannelContainer, error) {
	if err := p.validate(); err != nil {
		return nil, err
	}

	if p.ChannelId == 0 {
		return nil, ErrChannelIdIsNotSet
	}

	// check channel existence
	c, err := Cache.Channel.ById(p.ChannelId)
	if err != nil {
		return nil, err
	}

	// check if sender is whether a participant of conversation
	canOpen, err := c.CanOpen(p.AccountId)
	if err != nil {
		return nil, err
	}

	c.UpdatedAt = time.Now()
	// expensive
	if err = c.Update(); err != nil {
		return nil, err
	}

	if !canOpen {
		return nil, ErrCannotOpenChannel
	}

	cp, err := c.FetchParticipant(p.AccountId)
	if err != nil {
		return nil, err
	}

	oneSecondLater := time.Now().UTC().Add(time.Second * 1)
	// do not show unread count as 1 to user
	err = cp.RawUpdateLastSeenAt(oneSecondLater)
	if err != nil {
		return nil, err
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE

	if c.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	cm, err := p.createActivity(c, typeConstant)
	if err != nil {
		return nil, err
	}

	return p.buildContainer(c, cm)
}

func (p *ChannelRequest) Clone() *ChannelRequest {
	clone := new(ChannelRequest)
	*clone = *p

	return clone
}

func (p *ChannelRequest) AddJoinActivity(c *Channel, addedBy int64) error {
	if p.AccountId != addedBy && addedBy != 0 {
		addedByStr := strconv.FormatInt(addedBy, 10)
		p.Payload["addedBy"] = &addedByStr
	}

	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddLeaveActivity(c *Channel) error {
	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddInitActivity(c *Channel, participantIds []int64) (*ChannelMessage, error) {
	if len(participantIds) == 0 {
		return nil, nil
	}

	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	acc, err := Cache.Account.ById(c.CreatorId)
	if err != nil {
		return nil, err
	}

	if len(participantIds) > 0 {
		payload := formatParticipantIds(participantIds)
		p.Payload["initialParticipants"] = &payload
		activity := ChannelRequestMessage_TYPE_INIT
		p.Payload["systemType"] = &activity
		p.PopulateAddedBy(acc.Id)
	}

	cm, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) SetSystemMessageType(systemType string) {
	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	if systemType != "" {
		p.Payload["systemType"] = &systemType
	}
}

func (p *ChannelRequest) createActivity(c *Channel, typeConstant string) (*ChannelMessage, error) {
	cm, err := p.createMessage(c.Id, typeConstant)
	if err != nil {
		return nil, err
	}

	// add message to the channel
	cm.ClientRequestId = p.ClientRequestId
	_, err = c.AddMessage(cm)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) buildContainer(c *Channel, cm *ChannelMessage) (*ChannelContainer, error) {

	lastMessageContainer, err := cm.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	cm, err = lastMessageContainer.Message.PopulatePayload()
	if err != nil {
		return nil, err
	}

	cmc := NewChannelContainer()
	cmc.Channel = c
	cmc.IsParticipant = true
	cmc.LastMessage = lastMessageContainer
	cmc.LastMessage.Message = cm
	cmc.LastMessage.Message.ClientRequestId = p.ClientRequestId
	cmc.AddAccountOldId()
	if cmc.Err != nil {
		return nil, cmc.Err
	}

	return cmc, nil
}

func (p *ChannelRequest) validate() error {
	if p.AccountId == 0 {
		return ErrAccountIdIsNotSet
	}

	if p.GroupName == "" {
		p.GroupName = Channel_KODING_NAME
	}

	if p.TypeConstant == "" {
		p.TypeConstant = Channel_TYPE_PRIVATE_MESSAGE
	}

	return nil
}

// TODO refactor this function to use postgres
func (p *ChannelRequest) obtainParticipantIds() ([]int64, error) {
	participantIds := make([]int64, len(p.Recipients))
	for i, participantName := range p.Recipients {
		// get account from mongo
		account, err := modelhelper.GetAccount(participantName)
		if err != nil {
			return nil, err
		}

		a := NewAccount()
		socialApiId, _ := account.GetSocialApiId()

		a.Id = socialApiId
		a.OldId = account.Id.Hex()
		a.Nick = account.Profile.Nickname
		// fetch or create social api id
		if a.Id == 0 {
			if err := a.FetchOrCreate(); err != nil {
				return nil, err
			}
		}
		participantIds[i] = a.Id
	}

	// append creator to the recipients
	participantIds = prependCreatorId(participantIds, p.AccountId)

	// author and atleast one recipient should be in the
	// recipient list
	if len(participantIds) < 1 {
		// user can send private message to themself
		return nil, ErrRecipientsNotDefined
	}

	return participantIds, nil
}

func prependCreatorId(participants []int64, authorId int64) []int64 {
	participantIds := make([]int64, 0)
	participantIds = append(participantIds, authorId)

	for _, participant := range participants {
		if participant == authorId {
			continue
		}

		participantIds = append(participantIds, participant)
	}

	return participantIds
}

func (p *ChannelRequest) createMessage(channelId int64, typeConstant string) (*ChannelMessage, error) {
	cm := NewChannelMessage()
	cm.Body = getBody(p, typeConstant)
	cm.TypeConstant = typeConstant
	cm.AccountId = p.AccountId
	cm.InitialChannelId = channelId

	err := p.setPayloadWithTypeConstant(cm)
	if err != nil {
		return nil, err
	}

	if err := cm.Create(); err != nil {
		return nil, err
	}

	return cm, nil
}

// setPayloadWithTypeConstant sets the payload of the channel message
// if message type is the system, we dont need to send all payload data to the client
// we just need participants and systemType in the paylaod of the system message
func (p *ChannelRequest) setPayloadWithTypeConstant(cm *ChannelMessage) error {
	if cm.Payload == nil {
		cm.Payload = gorm.Hstore{}
	}

	if cm.TypeConstant == ChannelMessage_TYPE_SYSTEM {
		cm.Payload["systemType"] = p.Payload["systemType"]

		if p.Payload["initialParticipants"] != nil {
			cm.Payload["initialParticipants"] = p.Payload["initialParticipants"]
		}

		if p.Payload["addedBy"] != nil {
			cm.Payload["addedBy"] = p.Payload["addedBy"]
		}

	} else {
		cm.Payload = p.Payload
	}

	return nil
}

func getBody(p *ChannelRequest, typeConstant string) string {
	if typeConstant == ChannelMessage_TYPE_SYSTEM {
		return "system"
	}

	return p.Body
}

func formatParticipantIds(participantIds []int64) string {
	pids := make([]string, len(participantIds)-1)

	// exclude first participant since it is the private channel owner
	for i, participantId := range participantIds[1:len(participantIds)] {
		accountId := strconv.FormatInt(participantId, 10)
		pids[i] = accountId
	}

	result, _ := json.Marshal(pids)

	return string(result)
}

func (p *ChannelRequest) PopulateAddedBy(addedBy int64) {
	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	addedByStr := strconv.FormatInt(addedBy, 10)
	p.Payload["addedBy"] = &addedByStr
}
<commit_msg>socialapi: remove unnecessary fetchAcc function<commit_after>package models

import (
	"encoding/json"
	"koding/db/mongodb/modelhelper"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
)

const (
	ChannelRequestMessage_TYPE_INVITE = "invite"
	ChannelRequestMessage_TYPE_JOIN   = "join"
	ChannelRequestMessage_TYPE_LEAVE  = "leave"
	ChannelRequestMessage_TYPE_REJECT = "reject"
	ChannelRequestMessage_TYPE_KICK   = "kick"
	ChannelRequestMessage_TYPE_INIT   = "initiate"
)

type ChannelRequest struct {
	Body            string      `json:"body"`
	Name            string      `json:"name"`
	Payload         gorm.Hstore `json:"payload,omitempty"`
	GroupName       string      `json:"groupName"`
	Recipients      []string
	AccountId       int64  `json:"accountId,string"`
	ChannelId       int64  `json:"channelId,string"`
	RequestData     string `json:"requestData"`
	ClientRequestId string `json:"ClientRequestId"`
	Purpose         string `json:"purpose"`
	TypeConstant    string `json:"type"`
}

func (p *ChannelRequest) Create() (*ChannelContainer, error) {
	// validate the request
	if err := p.validate(); err != nil {
		return nil, err
	}

	// fetch participants
	participantIds, err := p.obtainParticipantIds()
	if err != nil {
		return nil, err
	}

	// create the channel
	c := NewChannel()
	c.GroupName = p.GroupName
	c.CreatorId = p.AccountId
	c.Name = RandomName()
	c.TypeConstant = p.TypeConstant
	c.Purpose = p.Purpose
	c.Payload = p.Payload

	if p.Name != "" {
		c.Name = p.Name
	}

	// all topic channels under koding, should be public
	if c.TypeConstant == Channel_TYPE_TOPIC && c.GroupName == Channel_KODING_NAME {
		c.PrivacyConstant = Channel_PRIVACY_PUBLIC
	}

	if err := c.Create(); err != nil {
		return nil, err
	}

	groupChannel, err := Cache.Channel.ByGroupName(c.GroupName)
	if err != nil {
		return nil, err
	}

	// add participants to the channel
	for _, participantId := range participantIds {
		// users should be in regarding group channel
		if err := checkForGroupParticipation(groupChannel, participantId); err != nil {
			return nil, err
		}

		cp, err := c.AddParticipant(participantId)
		if err != nil {
			continue
		}

		if participantId != p.AccountId {
			continue
		}

		// do not show unread count to the creator of the private message
		oneSecondLater := time.Now().UTC().Add(time.Second * 1)
		// do not show unread count as 1 to user
		err = cp.RawUpdateLastSeenAt(oneSecondLater)
		if err != nil {
			return nil, err
		}
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE
	if p.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	return p.buildInitContainer(c, participantIds, typeConstant)
}

func checkForGroupParticipation(groupChannel *Channel, participantId int64) error {
	// everyone is a member of koding group
	if groupChannel.GroupName == Channel_KODING_NAME {
		return nil
	}

	isParticipant, err := groupChannel.IsParticipant(participantId)
	if err != nil {
		return nil
	}

	if !isParticipant {
		return ErrCannotOpenChannel
	}

	return nil
}

func (p *ChannelRequest) buildInitContainer(c *Channel, participantIds []int64, typeConstant string) (*ChannelContainer, error) {

	np := &ChannelRequest{}
	*np = *p
	lastMessage, err := p.AddInitActivity(c, participantIds)
	if err != nil {
		return nil, err
	}

	if np.Body != "" {
		var err error
		// create private message

		lastMessage, err = p.createActivity(c, typeConstant)
		if err != nil {
			return nil, err
		}
	}

	cmc, err := p.buildContainer(c, lastMessage)
	if err != nil {
		return nil, err
	}

	participantOldIds, err := FetchAccountOldsIdByIdsFromCache(participantIds)
	if err != nil {
		return nil, err
	}

	// set participant count
	cmc.ParticipantCount = len(participantIds)
	// set preview
	cmc.ParticipantsPreview = participantOldIds

	return cmc, nil
}

func (p *ChannelRequest) Send() (*ChannelContainer, error) {
	if err := p.validate(); err != nil {
		return nil, err
	}

	if p.ChannelId == 0 {
		return nil, ErrChannelIdIsNotSet
	}

	// check channel existence
	c, err := Cache.Channel.ById(p.ChannelId)
	if err != nil {
		return nil, err
	}

	// check if sender is whether a participant of conversation
	canOpen, err := c.CanOpen(p.AccountId)
	if err != nil {
		return nil, err
	}

	c.UpdatedAt = time.Now()
	// expensive
	if err = c.Update(); err != nil {
		return nil, err
	}

	if !canOpen {
		return nil, ErrCannotOpenChannel
	}

	cp, err := c.FetchParticipant(p.AccountId)
	if err != nil {
		return nil, err
	}

	oneSecondLater := time.Now().UTC().Add(time.Second * 1)
	// do not show unread count as 1 to user
	err = cp.RawUpdateLastSeenAt(oneSecondLater)
	if err != nil {
		return nil, err
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE

	if c.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	cm, err := p.createActivity(c, typeConstant)
	if err != nil {
		return nil, err
	}

	return p.buildContainer(c, cm)
}

func (p *ChannelRequest) Clone() *ChannelRequest {
	clone := new(ChannelRequest)
	*clone = *p

	return clone
}

func (p *ChannelRequest) AddJoinActivity(c *Channel, addedBy int64) error {
	if p.AccountId != addedBy && addedBy != 0 {
		addedByStr := strconv.FormatInt(addedBy, 10)
		p.Payload["addedBy"] = &addedByStr
	}

	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddLeaveActivity(c *Channel) error {
	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddInitActivity(c *Channel, participantIds []int64) (*ChannelMessage, error) {
	if len(participantIds) == 0 {
		return nil, nil
	}

	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	if len(participantIds) > 0 {
		payload := formatParticipantIds(participantIds)
		p.Payload["initialParticipants"] = &payload
		activity := ChannelRequestMessage_TYPE_INIT
		p.Payload["systemType"] = &activity
		p.PopulateAddedBy(c.CreatorId)
	}

	cm, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) SetSystemMessageType(systemType string) {
	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	if systemType != "" {
		p.Payload["systemType"] = &systemType
	}
}

func (p *ChannelRequest) createActivity(c *Channel, typeConstant string) (*ChannelMessage, error) {
	cm, err := p.createMessage(c.Id, typeConstant)
	if err != nil {
		return nil, err
	}

	// add message to the channel
	cm.ClientRequestId = p.ClientRequestId
	_, err = c.AddMessage(cm)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) buildContainer(c *Channel, cm *ChannelMessage) (*ChannelContainer, error) {

	lastMessageContainer, err := cm.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	cm, err = lastMessageContainer.Message.PopulatePayload()
	if err != nil {
		return nil, err
	}

	cmc := NewChannelContainer()
	cmc.Channel = c
	cmc.IsParticipant = true
	cmc.LastMessage = lastMessageContainer
	cmc.LastMessage.Message = cm
	cmc.LastMessage.Message.ClientRequestId = p.ClientRequestId
	cmc.AddAccountOldId()
	if cmc.Err != nil {
		return nil, cmc.Err
	}

	return cmc, nil
}

func (p *ChannelRequest) validate() error {
	if p.AccountId == 0 {
		return ErrAccountIdIsNotSet
	}

	if p.GroupName == "" {
		p.GroupName = Channel_KODING_NAME
	}

	if p.TypeConstant == "" {
		p.TypeConstant = Channel_TYPE_PRIVATE_MESSAGE
	}

	return nil
}

// TODO refactor this function to use postgres
func (p *ChannelRequest) obtainParticipantIds() ([]int64, error) {
	participantIds := make([]int64, len(p.Recipients))
	for i, participantName := range p.Recipients {
		// get account from mongo
		account, err := modelhelper.GetAccount(participantName)
		if err != nil {
			return nil, err
		}

		a := NewAccount()
		socialApiId, _ := account.GetSocialApiId()

		a.Id = socialApiId
		a.OldId = account.Id.Hex()
		a.Nick = account.Profile.Nickname
		// fetch or create social api id
		if a.Id == 0 {
			if err := a.FetchOrCreate(); err != nil {
				return nil, err
			}
		}
		participantIds[i] = a.Id
	}

	// append creator to the recipients
	participantIds = prependCreatorId(participantIds, p.AccountId)

	// author and atleast one recipient should be in the
	// recipient list
	if len(participantIds) < 1 {
		// user can send private message to themself
		return nil, ErrRecipientsNotDefined
	}

	return participantIds, nil
}

func prependCreatorId(participants []int64, authorId int64) []int64 {
	participantIds := make([]int64, 0)
	participantIds = append(participantIds, authorId)

	for _, participant := range participants {
		if participant == authorId {
			continue
		}

		participantIds = append(participantIds, participant)
	}

	return participantIds
}

func (p *ChannelRequest) createMessage(channelId int64, typeConstant string) (*ChannelMessage, error) {
	cm := NewChannelMessage()
	cm.Body = getBody(p, typeConstant)
	cm.TypeConstant = typeConstant
	cm.AccountId = p.AccountId
	cm.InitialChannelId = channelId

	err := p.setPayloadWithTypeConstant(cm)
	if err != nil {
		return nil, err
	}

	if err := cm.Create(); err != nil {
		return nil, err
	}

	return cm, nil
}

// setPayloadWithTypeConstant sets the payload of the channel message
// if message type is the system, we dont need to send all payload data to the client
// we just need participants and systemType in the paylaod of the system message
func (p *ChannelRequest) setPayloadWithTypeConstant(cm *ChannelMessage) error {
	if cm.Payload == nil {
		cm.Payload = gorm.Hstore{}
	}

	if cm.TypeConstant == ChannelMessage_TYPE_SYSTEM {
		cm.Payload["systemType"] = p.Payload["systemType"]

		if p.Payload["initialParticipants"] != nil {
			cm.Payload["initialParticipants"] = p.Payload["initialParticipants"]
		}

		if p.Payload["addedBy"] != nil {
			cm.Payload["addedBy"] = p.Payload["addedBy"]
		}

	} else {
		cm.Payload = p.Payload
	}

	return nil
}

func getBody(p *ChannelRequest, typeConstant string) string {
	if typeConstant == ChannelMessage_TYPE_SYSTEM {
		return "system"
	}

	return p.Body
}

func formatParticipantIds(participantIds []int64) string {
	pids := make([]string, len(participantIds)-1)

	// exclude first participant since it is the private channel owner
	for i, participantId := range participantIds[1:len(participantIds)] {
		accountId := strconv.FormatInt(participantId, 10)
		pids[i] = accountId
	}

	result, _ := json.Marshal(pids)

	return string(result)
}

func (p *ChannelRequest) PopulateAddedBy(addedBy int64) {
	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	addedByStr := strconv.FormatInt(addedBy, 10)
	p.Payload["addedBy"] = &addedByStr
}
<|endoftext|>


<commit_before>package models

import (
	"encoding/json"
	"koding/db/mongodb/modelhelper"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
)

const (
	ChannelRequestMessage_TYPE_INVITE = "invite"
	ChannelRequestMessage_TYPE_JOIN   = "join"
	ChannelRequestMessage_TYPE_LEAVE  = "leave"
	ChannelRequestMessage_TYPE_REJECT = "reject"
	ChannelRequestMessage_TYPE_KICK   = "kick"
	ChannelRequestMessage_TYPE_INIT   = "initiate"
)

type ChannelRequest struct {
	Body            string      `json:"body"`
	Name            string      `json:"name"`
	Payload         gorm.Hstore `json:"payload,omitempty"`
	GroupName       string      `json:"groupName"`
	Recipients      []string
	AccountId       int64  `json:"accountId,string"`
	ChannelId       int64  `json:"channelId,string"`
	RequestData     string `json:"requestData"`
	ClientRequestId string `json:"ClientRequestId"`
	Purpose         string `json:"purpose"`
	TypeConstant    string `json:"type"`
}

func (p *ChannelRequest) Create() (*ChannelContainer, error) {
	// validate the request
	if err := p.validate(); err != nil {
		return nil, err
	}

	// fetch participants
	participantIds, err := p.obtainParticipantIds()
	if err != nil {
		return nil, err
	}

	// create the channel
	c := NewChannel()
	c.GroupName = p.GroupName
	c.CreatorId = p.AccountId
	c.Name = RandomName()
	c.TypeConstant = p.TypeConstant
	c.Purpose = p.Purpose
	c.Payload = p.Payload

	if p.Name != "" {
		c.Name = p.Name
	}

	// all topic channels under koding, should be public
	if c.TypeConstant == Channel_TYPE_TOPIC && c.GroupName == Channel_KODING_NAME {
		c.PrivacyConstant = Channel_PRIVACY_PUBLIC
	}

	if err := c.Create(); err != nil {
		return nil, err
	}

	groupChannel, err := Cache.Channel.ByGroupName(c.GroupName)
	if err != nil {
		return nil, err
	}

	// add participants to the channel
	for _, participantId := range participantIds {
		// users should be in regarding group channel
		if err := checkForGroupParticipation(groupChannel, participantId); err != nil {
			return nil, err
		}

		cp, err := c.AddParticipant(participantId)
		if err != nil {
			continue
		}

		if participantId != p.AccountId {
			continue
		}

		// do not show unread count to the creator of the private message
		oneSecondLater := time.Now().UTC().Add(time.Second * 1)
		// do not show unread count as 1 to user
		err = cp.RawUpdateLastSeenAt(oneSecondLater)
		if err != nil {
			return nil, err
		}
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE
	if p.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	return p.buildInitContainer(c, participantIds, typeConstant)
}

func checkForGroupParticipation(groupChannel *Channel, participantId int64) error {
	// everyone is a member of koding group
	if groupChannel.GroupName == Channel_KODING_NAME {
		return nil
	}

	isParticipant, err := groupChannel.IsParticipant(participantId)
	if err != nil {
		return nil
	}

	if !isParticipant {
		return ErrCannotOpenChannel
	}

	return nil
}

func (p *ChannelRequest) buildInitContainer(c *Channel, participantIds []int64, typeConstant string) (*ChannelContainer, error) {

	np := &ChannelRequest{}
	*np = *p
	lastMessage, err := p.AddInitActivity(c, participantIds)
	if err != nil {
		return nil, err
	}

	if np.Body != "" {
		var err error
		// create private message

		lastMessage, err = p.createActivity(c, typeConstant)
		if err != nil {
			return nil, err
		}
	}

	cmc, err := p.buildContainer(c, lastMessage)
	if err != nil {
		return nil, err
	}

	participantOldIds, err := FetchAccountOldsIdByIdsFromCache(participantIds)
	if err != nil {
		return nil, err
	}

	// set participant count
	cmc.ParticipantCount = len(participantIds)
	// set preview
	cmc.ParticipantsPreview = participantOldIds

	return cmc, nil
}

func (p *ChannelRequest) Send() (*ChannelContainer, error) {
	if err := p.validate(); err != nil {
		return nil, err
	}

	if p.ChannelId == 0 {
		return nil, ErrChannelIdIsNotSet
	}

	// check channel existence
	c, err := Cache.Channel.ById(p.ChannelId)
	if err != nil {
		return nil, err
	}

	// check if sender is whether a participant of conversation
	canOpen, err := c.CanOpen(p.AccountId)
	if err != nil {
		return nil, err
	}

	c.UpdatedAt = time.Now()
	// expensive
	if err = c.Update(); err != nil {
		return nil, err
	}

	if !canOpen {
		return nil, ErrCannotOpenChannel
	}

	cp, err := c.FetchParticipant(p.AccountId)
	if err != nil {
		return nil, err
	}

	oneSecondLater := time.Now().UTC().Add(time.Second * 1)
	// do not show unread count as 1 to user
	err = cp.RawUpdateLastSeenAt(oneSecondLater)
	if err != nil {
		return nil, err
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE

	if c.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	cm, err := p.createActivity(c, typeConstant)
	if err != nil {
		return nil, err
	}

	return p.buildContainer(c, cm)
}

func (p *ChannelRequest) Clone() *ChannelRequest {
	clone := new(ChannelRequest)
	*clone = *p

	return clone
}

func (p *ChannelRequest) AddJoinActivity(c *Channel, addedBy int64) error {
	if p.AccountId != addedBy && addedBy != 0 {
		addedByStr := strconv.FormatInt(addedBy, 10)
		p.Payload["addedBy"] = &addedByStr
	}

	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddLeaveActivity(c *Channel) error {
	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddInitActivity(c *Channel, participantIds []int64) (*ChannelMessage, error) {
	if len(participantIds) == 0 {
		return nil, nil
	}

	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	if len(participantIds) > 0 {
		payload := formatParticipantIds(participantIds)
		p.Payload["initialParticipants"] = &payload
		activity := ChannelRequestMessage_TYPE_INIT
		p.Payload["systemType"] = &activity
	}

	cm, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) SetSystemMessageType(systemType string) {
	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	if systemType != "" {
		p.Payload["systemType"] = &systemType
	}
}

func (p *ChannelRequest) createActivity(c *Channel, typeConstant string) (*ChannelMessage, error) {
	cm, err := p.createMessage(c.Id, typeConstant)
	if err != nil {
		return nil, err
	}

	// add message to the channel
	cm.ClientRequestId = p.ClientRequestId
	_, err = c.AddMessage(cm)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) buildContainer(c *Channel, cm *ChannelMessage) (*ChannelContainer, error) {

	lastMessageContainer, err := cm.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	cm, err = lastMessageContainer.Message.PopulatePayload()
	if err != nil {
		return nil, err
	}

	cmc := NewChannelContainer()
	cmc.Channel = c
	cmc.IsParticipant = true
	cmc.LastMessage = lastMessageContainer
	cmc.LastMessage.Message = cm
	cmc.LastMessage.Message.ClientRequestId = p.ClientRequestId
	cmc.AddAccountOldId()
	if cmc.Err != nil {
		return nil, cmc.Err
	}

	return cmc, nil
}

func (p *ChannelRequest) validate() error {
	if p.AccountId == 0 {
		return ErrAccountIdIsNotSet
	}

	if p.GroupName == "" {
		p.GroupName = Channel_KODING_NAME
	}

	if p.TypeConstant == "" {
		p.TypeConstant = Channel_TYPE_PRIVATE_MESSAGE
	}

	return nil
}

// TODO refactor this function to use postgres
func (p *ChannelRequest) obtainParticipantIds() ([]int64, error) {
	participantIds := make([]int64, len(p.Recipients))
	for i, participantName := range p.Recipients {
		// get account from mongo
		account, err := modelhelper.GetAccount(participantName)
		if err != nil {
			return nil, err
		}

		a := NewAccount()
		socialApiId, _ := account.GetSocialApiId()

		a.Id = socialApiId
		a.OldId = account.Id.Hex()
		a.Nick = account.Profile.Nickname
		// fetch or create social api id
		if a.Id == 0 {
			if err := a.FetchOrCreate(); err != nil {
				return nil, err
			}
		}
		participantIds[i] = a.Id
	}

	// append creator to the recipients
	participantIds = prependCreatorId(participantIds, p.AccountId)

	// author and atleast one recipient should be in the
	// recipient list
	if len(participantIds) < 1 {
		// user can send private message to themself
		return nil, ErrRecipientsNotDefined
	}

	return participantIds, nil
}

func prependCreatorId(participants []int64, authorId int64) []int64 {
	participantIds := make([]int64, 0)
	participantIds = append(participantIds, authorId)

	for _, participant := range participants {
		if participant == authorId {
			continue
		}

		participantIds = append(participantIds, participant)
	}

	return participantIds
}

func (p *ChannelRequest) createMessage(channelId int64, typeConstant string) (*ChannelMessage, error) {
	cm := NewChannelMessage()
	cm.Body = getBody(p, typeConstant)
	cm.TypeConstant = typeConstant
	cm.AccountId = p.AccountId
	cm.InitialChannelId = channelId

	err := p.setPayloadWithTypeConstant(cm)
	if err != nil {
		return nil, err
	}

	if err := cm.Create(); err != nil {
		return nil, err
	}

	return cm, nil
}

// setPayloadWithTypeConstant sets the payload of the channel message
// if message type is the system, we dont need to send all payload data to the client
// we just need participants and systemType in the paylaod of the system message
func (p *ChannelRequest) setPayloadWithTypeConstant(cm *ChannelMessage) error {
	if cm.Payload == nil {
		cm.Payload = gorm.Hstore{}
	}

	if cm.TypeConstant == ChannelMessage_TYPE_SYSTEM {
		cm.Payload["systemType"] = p.Payload["systemType"]

		if p.Payload["initialParticipants"] != nil {
			cm.Payload["initialParticipants"] = p.Payload["initialParticipants"]
		}

		if p.Payload["addedBy"] != nil {
			cm.Payload["addedBy"] = p.Payload["addedBy"]
		}

	} else {
		cm.Payload = p.Payload
	}

	return nil
}

func getBody(p *ChannelRequest, typeConstant string) string {
	if typeConstant == ChannelMessage_TYPE_SYSTEM {
		return "system"
	}

	return p.Body
}

func formatParticipantIds(participantIds []int64) string {
	pids := make([]string, len(participantIds)-1)

	// exclude first participant since it is the private channel owner
	for i, participantId := range participantIds[1:len(participantIds)] {
		accountId := strconv.FormatInt(participantId, 10)
		pids[i] = accountId
	}

	result, _ := json.Marshal(pids)

	return string(result)
}
<commit_msg>socialapi: add addedBy into payload<commit_after>package models

import (
	"encoding/json"
	"koding/db/mongodb/modelhelper"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
)

const (
	ChannelRequestMessage_TYPE_INVITE = "invite"
	ChannelRequestMessage_TYPE_JOIN   = "join"
	ChannelRequestMessage_TYPE_LEAVE  = "leave"
	ChannelRequestMessage_TYPE_REJECT = "reject"
	ChannelRequestMessage_TYPE_KICK   = "kick"
	ChannelRequestMessage_TYPE_INIT   = "initiate"
)

type ChannelRequest struct {
	Body            string      `json:"body"`
	Name            string      `json:"name"`
	Payload         gorm.Hstore `json:"payload,omitempty"`
	GroupName       string      `json:"groupName"`
	Recipients      []string
	AccountId       int64  `json:"accountId,string"`
	ChannelId       int64  `json:"channelId,string"`
	RequestData     string `json:"requestData"`
	ClientRequestId string `json:"ClientRequestId"`
	Purpose         string `json:"purpose"`
	TypeConstant    string `json:"type"`
}

func (p *ChannelRequest) Create() (*ChannelContainer, error) {
	// validate the request
	if err := p.validate(); err != nil {
		return nil, err
	}

	// fetch participants
	participantIds, err := p.obtainParticipantIds()
	if err != nil {
		return nil, err
	}

	// create the channel
	c := NewChannel()
	c.GroupName = p.GroupName
	c.CreatorId = p.AccountId
	c.Name = RandomName()
	c.TypeConstant = p.TypeConstant
	c.Purpose = p.Purpose
	c.Payload = p.Payload

	if p.Name != "" {
		c.Name = p.Name
	}

	// all topic channels under koding, should be public
	if c.TypeConstant == Channel_TYPE_TOPIC && c.GroupName == Channel_KODING_NAME {
		c.PrivacyConstant = Channel_PRIVACY_PUBLIC
	}

	if err := c.Create(); err != nil {
		return nil, err
	}

	groupChannel, err := Cache.Channel.ByGroupName(c.GroupName)
	if err != nil {
		return nil, err
	}

	// add participants to the channel
	for _, participantId := range participantIds {
		// users should be in regarding group channel
		if err := checkForGroupParticipation(groupChannel, participantId); err != nil {
			return nil, err
		}

		cp, err := c.AddParticipant(participantId)
		if err != nil {
			continue
		}

		if participantId != p.AccountId {
			continue
		}

		// do not show unread count to the creator of the private message
		oneSecondLater := time.Now().UTC().Add(time.Second * 1)
		// do not show unread count as 1 to user
		err = cp.RawUpdateLastSeenAt(oneSecondLater)
		if err != nil {
			return nil, err
		}
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE
	if p.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	return p.buildInitContainer(c, participantIds, typeConstant)
}

func checkForGroupParticipation(groupChannel *Channel, participantId int64) error {
	// everyone is a member of koding group
	if groupChannel.GroupName == Channel_KODING_NAME {
		return nil
	}

	isParticipant, err := groupChannel.IsParticipant(participantId)
	if err != nil {
		return nil
	}

	if !isParticipant {
		return ErrCannotOpenChannel
	}

	return nil
}

func (p *ChannelRequest) buildInitContainer(c *Channel, participantIds []int64, typeConstant string) (*ChannelContainer, error) {

	np := &ChannelRequest{}
	*np = *p
	lastMessage, err := p.AddInitActivity(c, participantIds)
	if err != nil {
		return nil, err
	}

	if np.Body != "" {
		var err error
		// create private message

		lastMessage, err = p.createActivity(c, typeConstant)
		if err != nil {
			return nil, err
		}
	}

	cmc, err := p.buildContainer(c, lastMessage)
	if err != nil {
		return nil, err
	}

	participantOldIds, err := FetchAccountOldsIdByIdsFromCache(participantIds)
	if err != nil {
		return nil, err
	}

	// set participant count
	cmc.ParticipantCount = len(participantIds)
	// set preview
	cmc.ParticipantsPreview = participantOldIds

	return cmc, nil
}

func (p *ChannelRequest) Send() (*ChannelContainer, error) {
	if err := p.validate(); err != nil {
		return nil, err
	}

	if p.ChannelId == 0 {
		return nil, ErrChannelIdIsNotSet
	}

	// check channel existence
	c, err := Cache.Channel.ById(p.ChannelId)
	if err != nil {
		return nil, err
	}

	// check if sender is whether a participant of conversation
	canOpen, err := c.CanOpen(p.AccountId)
	if err != nil {
		return nil, err
	}

	c.UpdatedAt = time.Now()
	// expensive
	if err = c.Update(); err != nil {
		return nil, err
	}

	if !canOpen {
		return nil, ErrCannotOpenChannel
	}

	cp, err := c.FetchParticipant(p.AccountId)
	if err != nil {
		return nil, err
	}

	oneSecondLater := time.Now().UTC().Add(time.Second * 1)
	// do not show unread count as 1 to user
	err = cp.RawUpdateLastSeenAt(oneSecondLater)
	if err != nil {
		return nil, err
	}

	typeConstant := ChannelMessage_TYPE_PRIVATE_MESSAGE

	if c.TypeConstant == Channel_TYPE_TOPIC {
		typeConstant = ChannelMessage_TYPE_POST
	}

	cm, err := p.createActivity(c, typeConstant)
	if err != nil {
		return nil, err
	}

	return p.buildContainer(c, cm)
}

func (p *ChannelRequest) Clone() *ChannelRequest {
	clone := new(ChannelRequest)
	*clone = *p

	return clone
}

func (p *ChannelRequest) AddJoinActivity(c *Channel, addedBy int64) error {
	if p.AccountId != addedBy && addedBy != 0 {
		addedByStr := strconv.FormatInt(addedBy, 10)
		p.Payload["addedBy"] = &addedByStr
	}

	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddLeaveActivity(c *Channel) error {
	_, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)

	return err
}

func (p *ChannelRequest) AddInitActivity(c *Channel, participantIds []int64) (*ChannelMessage, error) {
	if len(participantIds) == 0 {
		return nil, nil
	}

	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	acc, err := Cache.Account.ById(c.CreatorId)
	if err != nil {
		return nil, err
	}

	if len(participantIds) > 0 {
		payload := formatParticipantIds(participantIds)
		p.Payload["initialParticipants"] = &payload
		activity := ChannelRequestMessage_TYPE_INIT
		p.Payload["systemType"] = &activity
		p.PopulateAddedBy(acc.Id)
	}

	cm, err := p.createActivity(c, ChannelMessage_TYPE_SYSTEM)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) SetSystemMessageType(systemType string) {
	if p.Payload == nil {
		p.Payload = gorm.Hstore{}
	}

	if systemType != "" {
		p.Payload["systemType"] = &systemType
	}
}

func (p *ChannelRequest) createActivity(c *Channel, typeConstant string) (*ChannelMessage, error) {
	cm, err := p.createMessage(c.Id, typeConstant)
	if err != nil {
		return nil, err
	}

	// add message to the channel
	cm.ClientRequestId = p.ClientRequestId
	_, err = c.AddMessage(cm)
	if err != nil {
		return nil, err
	}

	return cm, nil
}

func (p *ChannelRequest) buildContainer(c *Channel, cm *ChannelMessage) (*ChannelContainer, error) {

	lastMessageContainer, err := cm.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	cm, err = lastMessageContainer.Message.PopulatePayload()
	if err != nil {
		return nil, err
	}

	cmc := NewChannelContainer()
	cmc.Channel = c
	cmc.IsParticipant = true
	cmc.LastMessage = lastMessageContainer
	cmc.LastMessage.Message = cm
	cmc.LastMessage.Message.ClientRequestId = p.ClientRequestId
	cmc.AddAccountOldId()
	if cmc.Err != nil {
		return nil, cmc.Err
	}

	return cmc, nil
}

func (p *ChannelRequest) validate() error {
	if p.AccountId == 0 {
		return ErrAccountIdIsNotSet
	}

	if p.GroupName == "" {
		p.GroupName = Channel_KODING_NAME
	}

	if p.TypeConstant == "" {
		p.TypeConstant = Channel_TYPE_PRIVATE_MESSAGE
	}

	return nil
}

// TODO refactor this function to use postgres
func (p *ChannelRequest) obtainParticipantIds() ([]int64, error) {
	participantIds := make([]int64, len(p.Recipients))
	for i, participantName := range p.Recipients {
		// get account from mongo
		account, err := modelhelper.GetAccount(participantName)
		if err != nil {
			return nil, err
		}

		a := NewAccount()
		socialApiId, _ := account.GetSocialApiId()

		a.Id = socialApiId
		a.OldId = account.Id.Hex()
		a.Nick = account.Profile.Nickname
		// fetch or create social api id
		if a.Id == 0 {
			if err := a.FetchOrCreate(); err != nil {
				return nil, err
			}
		}
		participantIds[i] = a.Id
	}

	// append creator to the recipients
	participantIds = prependCreatorId(participantIds, p.AccountId)

	// author and atleast one recipient should be in the
	// recipient list
	if len(participantIds) < 1 {
		// user can send private message to themself
		return nil, ErrRecipientsNotDefined
	}

	return participantIds, nil
}

func prependCreatorId(participants []int64, authorId int64) []int64 {
	participantIds := make([]int64, 0)
	participantIds = append(participantIds, authorId)

	for _, participant := range participants {
		if participant == authorId {
			continue
		}

		participantIds = append(participantIds, participant)
	}

	return participantIds
}

func (p *ChannelRequest) createMessage(channelId int64, typeConstant string) (*ChannelMessage, error) {
	cm := NewChannelMessage()
	cm.Body = getBody(p, typeConstant)
	cm.TypeConstant = typeConstant
	cm.AccountId = p.AccountId
	cm.InitialChannelId = channelId

	err := p.setPayloadWithTypeConstant(cm)
	if err != nil {
		return nil, err
	}

	if err := cm.Create(); err != nil {
		return nil, err
	}

	return cm, nil
}

// setPayloadWithTypeConstant sets the payload of the channel message
// if message type is the system, we dont need to send all payload data to the client
// we just need participants and systemType in the paylaod of the system message
func (p *ChannelRequest) setPayloadWithTypeConstant(cm *ChannelMessage) error {
	if cm.Payload == nil {
		cm.Payload = gorm.Hstore{}
	}

	if cm.TypeConstant == ChannelMessage_TYPE_SYSTEM {
		cm.Payload["systemType"] = p.Payload["systemType"]

		if p.Payload["initialParticipants"] != nil {
			cm.Payload["initialParticipants"] = p.Payload["initialParticipants"]
		}

		if p.Payload["addedBy"] != nil {
			cm.Payload["addedBy"] = p.Payload["addedBy"]
		}

	} else {
		cm.Payload = p.Payload
	}

	return nil
}

func getBody(p *ChannelRequest, typeConstant string) string {
	if typeConstant == ChannelMessage_TYPE_SYSTEM {
		return "system"
	}

	return p.Body
}

func formatParticipantIds(participantIds []int64) string {
	pids := make([]string, len(participantIds)-1)

	// exclude first participant since it is the private channel owner
	for i, participantId := range participantIds[1:len(participantIds)] {
		accountId := strconv.FormatInt(participantId, 10)
		pids[i] = accountId
	}

	result, _ := json.Marshal(pids)

	return string(result)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package leaderelection

import (
	"fmt"
	"sync"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	core "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/record"
	"k8s.io/kubernetes/pkg/api/v1"
	fakeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
	rl "k8s.io/kubernetes/pkg/client/leaderelection/resourcelock"
)

// Will test leader election using endpoints as the resource
func TestTryAcquireOrRenewEndpoints(t *testing.T) {
	future := time.Now().Add(1000 * time.Hour)
	past := time.Now().Add(-1000 * time.Hour)

	tests := []struct {
		observedRecord rl.LeaderElectionRecord
		observedTime   time.Time
		reactors       []struct {
			verb     string
			reaction core.ReactionFunc
		}

		expectSuccess    bool
		transitionLeader bool
		outHolder        string
	}{
		// acquire from no endpoints
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, nil, errors.NewNotFound(action.(core.GetAction).GetResource().GroupResource(), action.(core.GetAction).GetName())
					},
				},
				{
					verb: "create",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.Endpoints), nil
					},
				},
			},
			expectSuccess: true,
			outHolder:     "baz",
		},
		// acquire from unled endpoints
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.Endpoints{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
							},
						}, nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.Endpoints), nil
					},
				},
			},

			expectSuccess:    true,
			transitionLeader: true,
			outHolder:        "baz",
		},
		// acquire from led, unacked endpoints
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.Endpoints{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
								Annotations: map[string]string{
									rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`,
								},
							},
						}, nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.Endpoints), nil
					},
				},
			},
			observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
			observedTime:   past,

			expectSuccess:    true,
			transitionLeader: true,
			outHolder:        "baz",
		},
		// don't acquire from led, acked endpoints
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.Endpoints{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
								Annotations: map[string]string{
									rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`,
								},
							},
						}, nil
					},
				},
			},
			observedTime: future,

			expectSuccess: false,
			outHolder:     "bing",
		},
		// renew already acquired endpoints
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.Endpoints{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
								Annotations: map[string]string{
									rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"baz"}`,
								},
							},
						}, nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.Endpoints), nil
					},
				},
			},
			observedTime:   future,
			observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"},

			expectSuccess: true,
			outHolder:     "baz",
		},
	}

	for i, test := range tests {
		// OnNewLeader is called async so we have to wait for it.
		var wg sync.WaitGroup
		wg.Add(1)
		var reportedLeader string

		lock := rl.EndpointsLock{
			EndpointsMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
			LockConfig: rl.ResourceLockConfig{
				Identity:      "baz",
				EventRecorder: &record.FakeRecorder{},
			},
		}

		lec := LeaderElectionConfig{
			Lock:          &lock,
			LeaseDuration: 10 * time.Second,
			Callbacks: LeaderCallbacks{
				OnNewLeader: func(l string) {
					defer wg.Done()
					reportedLeader = l
				},
			},
		}
		c := &fakeclientset.Clientset{Fake: core.Fake{}}
		for _, reactor := range test.reactors {
			c.AddReactor(reactor.verb, "endpoints", reactor.reaction)
		}
		c.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
			t.Errorf("[%v] unreachable action. testclient called too many times: %+v", i, action)
			return true, nil, fmt.Errorf("uncreachable action")
		})

		le := &LeaderElector{
			config:         lec,
			observedRecord: test.observedRecord,
			observedTime:   test.observedTime,
		}
		lock.Client = c

		if test.expectSuccess != le.tryAcquireOrRenew() {
			t.Errorf("[%v]unexpected result of tryAcquireOrRenew: [succeded=%v]", i, !test.expectSuccess)
		}

		le.observedRecord.AcquireTime = metav1.Time{}
		le.observedRecord.RenewTime = metav1.Time{}
		if le.observedRecord.HolderIdentity != test.outHolder {
			t.Errorf("[%v]expected holder:\n\t%+v\ngot:\n\t%+v", i, test.outHolder, le.observedRecord.HolderIdentity)
		}
		if len(test.reactors) != len(c.Actions()) {
			t.Errorf("[%v]wrong number of api interactions", i)
		}
		if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 {
			t.Errorf("[%v]leader should have transitioned but did not", i)
		}
		if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 {
			t.Errorf("[%v]leader should not have transitioned but did", i)
		}

		le.maybeReportTransition()
		wg.Wait()
		if reportedLeader != test.outHolder {
			t.Errorf("[%v]reported leader was not the new leader. expected %q, got %q", i, test.outHolder, reportedLeader)
		}
	}
}

// Will test leader election using configmap as the resource
func TestTryAcquireOrRenewConfigMap(t *testing.T) {
	future := time.Now().Add(1000 * time.Hour)
	past := time.Now().Add(-1000 * time.Hour)

	tests := []struct {
		observedRecord rl.LeaderElectionRecord
		observedTime   time.Time
		reactors       []struct {
			verb     string
			reaction core.ReactionFunc
		}

		expectSuccess    bool
		transitionLeader bool
		outHolder        string
	}{
		// acquire from no onfigmap
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, nil, errors.NewNotFound(action.(core.GetAction).GetResource().GroupResource(), action.(core.GetAction).GetName())
					},
				},
				{
					verb: "create",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.ConfigMap), nil
					},
				},
			},
			expectSuccess: true,
			outHolder:     "baz",
		},
		// acquire from unled configmap
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.ConfigMap{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
							},
						}, nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.ConfigMap), nil
					},
				},
			},

			expectSuccess:    true,
			transitionLeader: true,
			outHolder:        "baz",
		},
		// acquire from led, unacked configmap
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.ConfigMap{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
								Annotations: map[string]string{
									rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`,
								},
							},
						}, nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.ConfigMap), nil
					},
				},
			},
			observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
			observedTime:   past,

			expectSuccess:    true,
			transitionLeader: true,
			outHolder:        "baz",
		},
		// don't acquire from led, acked configmap
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.ConfigMap{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
								Annotations: map[string]string{
									rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`,
								},
							},
						}, nil
					},
				},
			},
			observedTime: future,

			expectSuccess: false,
			outHolder:     "bing",
		},
		// renew already acquired configmap
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, &v1.ConfigMap{
							ObjectMeta: metav1.ObjectMeta{
								Namespace: action.GetNamespace(),
								Name:      action.(core.GetAction).GetName(),
								Annotations: map[string]string{
									rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"baz"}`,
								},
							},
						}, nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject().(*v1.ConfigMap), nil
					},
				},
			},
			observedTime:   future,
			observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"},

			expectSuccess: true,
			outHolder:     "baz",
		},
	}

	for i, test := range tests {
		// OnNewLeader is called async so we have to wait for it.
		var wg sync.WaitGroup
		wg.Add(1)
		var reportedLeader string

		lock := rl.ConfigMapLock{
			ConfigMapMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
			LockConfig: rl.ResourceLockConfig{
				Identity:      "baz",
				EventRecorder: &record.FakeRecorder{},
			},
		}

		lec := LeaderElectionConfig{
			Lock:          &lock,
			LeaseDuration: 10 * time.Second,
			Callbacks: LeaderCallbacks{
				OnNewLeader: func(l string) {
					defer wg.Done()
					reportedLeader = l
				},
			},
		}
		c := &fakeclientset.Clientset{Fake: core.Fake{}}
		for _, reactor := range test.reactors {
			c.AddReactor(reactor.verb, "configmaps", reactor.reaction)
		}
		c.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
			t.Errorf("[%v] unreachable action. testclient called too many times: %+v", i, action)
			return true, nil, fmt.Errorf("uncreachable action")
		})

		le := &LeaderElector{
			config:         lec,
			observedRecord: test.observedRecord,
			observedTime:   test.observedTime,
		}
		lock.Client = c

		if test.expectSuccess != le.tryAcquireOrRenew() {
			t.Errorf("[%v]unexpected result of tryAcquireOrRenew: [succeded=%v]", i, !test.expectSuccess)
		}

		le.observedRecord.AcquireTime = metav1.Time{}
		le.observedRecord.RenewTime = metav1.Time{}
		if le.observedRecord.HolderIdentity != test.outHolder {
			t.Errorf("[%v]expected holder:\n\t%+v\ngot:\n\t%+v", i, test.outHolder, le.observedRecord.HolderIdentity)
		}
		if len(test.reactors) != len(c.Actions()) {
			t.Errorf("[%v]wrong number of api interactions", i)
		}
		if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 {
			t.Errorf("[%v]leader should have transitioned but did not", i)
		}
		if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 {
			t.Errorf("[%v]leader should not have transitioned but did", i)
		}

		le.maybeReportTransition()
		wg.Wait()
		if reportedLeader != test.outHolder {
			t.Errorf("[%v]reported leader was not the new leader. expected %q, got %q", i, test.outHolder, reportedLeader)
		}
	}
}
<commit_msg>Refactor leader election test code to remove redundant code<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package leaderelection

import (
	"fmt"
	"sync"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	core "k8s.io/client-go/testing"
	"k8s.io/client-go/tools/record"
	"k8s.io/kubernetes/pkg/api/v1"
	fakeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
	rl "k8s.io/kubernetes/pkg/client/leaderelection/resourcelock"
)

func createLockObject(objectType string, objectMeta metav1.ObjectMeta) (obj runtime.Object) {
	switch objectType {
	case "endpoints":
		obj = &v1.Endpoints{ObjectMeta: objectMeta}
	case "configmaps":
		obj = &v1.ConfigMap{ObjectMeta: objectMeta}
	default:
		panic("unexpected objType:" + objectType)
	}
	return
}

// Will test leader election using endpoints as the resource
func TestTryAcquireOrRenewEndpoints(t *testing.T) {
	testTryAcquireOrRenew(t, "endpoints")
}

func testTryAcquireOrRenew(t *testing.T, objectType string) {
	future := time.Now().Add(1000 * time.Hour)
	past := time.Now().Add(-1000 * time.Hour)

	tests := []struct {
		observedRecord rl.LeaderElectionRecord
		observedTime   time.Time
		reactors       []struct {
			verb     string
			reaction core.ReactionFunc
		}

		expectSuccess    bool
		transitionLeader bool
		outHolder        string
	}{
		// acquire from no object
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, nil, errors.NewNotFound(action.(core.GetAction).GetResource().GroupResource(), action.(core.GetAction).GetName())
					},
				},
				{
					verb: "create",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject(), nil
					},
				},
			},
			expectSuccess: true,
			outHolder:     "baz",
		},
		// acquire from unled object
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						objectMeta := metav1.ObjectMeta{
							Namespace: action.GetNamespace(),
							Name:      action.(core.GetAction).GetName(),
						}
						return true, createLockObject(objectType, objectMeta), nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject(), nil
					},
				},
			},

			expectSuccess:    true,
			transitionLeader: true,
			outHolder:        "baz",
		},
		// acquire from led, unacked object
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						objectMeta := metav1.ObjectMeta{
							Namespace: action.GetNamespace(),
							Name:      action.(core.GetAction).GetName(),
							Annotations: map[string]string{
								rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`,
							},
						}
						return true, createLockObject(objectType, objectMeta), nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject(), nil
					},
				},
			},
			observedRecord: rl.LeaderElectionRecord{HolderIdentity: "bing"},
			observedTime:   past,

			expectSuccess:    true,
			transitionLeader: true,
			outHolder:        "baz",
		},
		// don't acquire from led, acked object
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						objectMeta := metav1.ObjectMeta{
							Namespace: action.GetNamespace(),
							Name:      action.(core.GetAction).GetName(),
							Annotations: map[string]string{
								rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`,
							},
						}
						return true, createLockObject(objectType, objectMeta), nil
					},
				},
			},
			observedTime: future,

			expectSuccess: false,
			outHolder:     "bing",
		},
		// renew already acquired object
		{
			reactors: []struct {
				verb     string
				reaction core.ReactionFunc
			}{
				{
					verb: "get",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						objectMeta := metav1.ObjectMeta{
							Namespace: action.GetNamespace(),
							Name:      action.(core.GetAction).GetName(),
							Annotations: map[string]string{
								rl.LeaderElectionRecordAnnotationKey: `{"holderIdentity":"baz"}`,
							},
						}
						return true, createLockObject(objectType, objectMeta), nil
					},
				},
				{
					verb: "update",
					reaction: func(action core.Action) (handled bool, ret runtime.Object, err error) {
						return true, action.(core.CreateAction).GetObject(), nil
					},
				},
			},
			observedTime:   future,
			observedRecord: rl.LeaderElectionRecord{HolderIdentity: "baz"},

			expectSuccess: true,
			outHolder:     "baz",
		},
	}

	for i, test := range tests {
		// OnNewLeader is called async so we have to wait for it.
		var wg sync.WaitGroup
		wg.Add(1)
		var reportedLeader string
		var lock rl.Interface

		objectMeta := metav1.ObjectMeta{Namespace: "foo", Name: "bar"}
		resourceLockConfig := rl.ResourceLockConfig{
			Identity:      "baz",
			EventRecorder: &record.FakeRecorder{},
		}
		c := &fakeclientset.Clientset{Fake: core.Fake{}}
		for _, reactor := range test.reactors {
			c.AddReactor(reactor.verb, objectType, reactor.reaction)
		}
		c.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
			t.Errorf("[%v] unreachable action. testclient called too many times: %+v", i, action)
			return true, nil, fmt.Errorf("unreachable action")
		})

		switch objectType {
		case "endpoints":
			lock = &rl.EndpointsLock{
				EndpointsMeta: objectMeta,
				LockConfig:    resourceLockConfig,
				Client:        c,
			}
		case "configmaps":
			lock = &rl.ConfigMapLock{
				ConfigMapMeta: objectMeta,
				LockConfig:    resourceLockConfig,
				Client:        c,
			}
		}

		lec := LeaderElectionConfig{
			Lock:          lock,
			LeaseDuration: 10 * time.Second,
			Callbacks: LeaderCallbacks{
				OnNewLeader: func(l string) {
					defer wg.Done()
					reportedLeader = l
				},
			},
		}
		le := &LeaderElector{
			config:         lec,
			observedRecord: test.observedRecord,
			observedTime:   test.observedTime,
		}

		if test.expectSuccess != le.tryAcquireOrRenew() {
			t.Errorf("[%v]unexpected result of tryAcquireOrRenew: [succeded=%v]", i, !test.expectSuccess)
		}

		le.observedRecord.AcquireTime = metav1.Time{}
		le.observedRecord.RenewTime = metav1.Time{}
		if le.observedRecord.HolderIdentity != test.outHolder {
			t.Errorf("[%v]expected holder:\n\t%+v\ngot:\n\t%+v", i, test.outHolder, le.observedRecord.HolderIdentity)
		}
		if len(test.reactors) != len(c.Actions()) {
			t.Errorf("[%v]wrong number of api interactions", i)
		}
		if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 {
			t.Errorf("[%v]leader should have transitioned but did not", i)
		}
		if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 {
			t.Errorf("[%v]leader should not have transitioned but did", i)
		}

		le.maybeReportTransition()
		wg.Wait()
		if reportedLeader != test.outHolder {
			t.Errorf("[%v]reported leader was not the new leader. expected %q, got %q", i, test.outHolder, reportedLeader)
		}
	}
}

// Will test leader election using configmap as the resource
func TestTryAcquireOrRenewConfigMaps(t *testing.T) {
	testTryAcquireOrRenew(t, "configmaps")
}
<|endoftext|>


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

package webhook

import (
	"fmt"
	"strings"

	v1 "k8s.io/api/admissionregistration/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	klabels "k8s.io/apimachinery/pkg/labels"

	"istio.io/api/label"
	"istio.io/istio/pilot/pkg/util/sets"
	"istio.io/istio/pkg/config/analysis"
	"istio.io/istio/pkg/config/analysis/msg"
	"istio.io/istio/pkg/config/resource"
	"istio.io/istio/pkg/config/schema/collection"
	"istio.io/istio/pkg/config/schema/collections"
)

var (
	webhookCol = collections.K8SAdmissionregistrationK8SIoV1Mutatingwebhookconfigurations.Name()
	serviceCol = collections.K8SCoreV1Services.Name()
)

type Analyzer struct {
	SkipServiceCheck bool
}

var _ analysis.Analyzer = &Analyzer{}

func (a *Analyzer) Metadata() analysis.Metadata {
	return analysis.Metadata{
		Name:        "webhook.Analyzer",
		Description: "Checks the validity of Istio webhooks",
		Inputs: collection.Names{
			webhookCol,
			serviceCol,
		},
	}
}

func getNamespaceLabels() []klabels.Set {
	return []klabels.Set{
		{},
		{"istio-injection": "enabled"},
		{"istio-injection": "disabled"},
	}
}

func getObjectLabels() []klabels.Set {
	return []klabels.Set{
		{},
		{"sidecar.istio.io/inject": "true"},
		{"sidecar.istio.io/inject": "false"},
	}
}

func (a *Analyzer) Analyze(context analysis.Context) {
	// First, extract and index all webhooks we found
	webhooks := map[string][]v1.MutatingWebhook{}
	resources := map[string]*resource.Instance{}
	revisions := sets.NewSet()
	context.ForEach(webhookCol, func(resource *resource.Instance) bool {
		wh := resource.Message.(*v1.MutatingWebhookConfiguration)
		revs := extractRevisions(wh)
		if len(revs) == 0 && !isIstioWebhook(wh) {
			return true
		}
		webhooks[resource.Metadata.FullName.String()] = wh.Webhooks
		for _, h := range wh.Webhooks {
			resources[fmt.Sprintf("%v/%v", resource.Metadata.FullName.String(), h.Name)] = resource
		}
		revisions.Insert(revs...)
		return true
	})

	// Set up all relevant namespace and object selector permutations
	namespaceLabels := getNamespaceLabels()
	for rev := range revisions {
		for _, base := range getNamespaceLabels() {
			base[label.IoIstioRev.Name] = rev
			namespaceLabels = append(namespaceLabels, base)
		}
	}
	objectLabels := getObjectLabels()
	for rev := range revisions {
		for _, base := range getObjectLabels() {
			base[label.IoIstioRev.Name] = rev
			objectLabels = append(objectLabels, base)
		}
	}

	// For each permutation, we check which webhooks it matches. It must match exactly 0 or 1!
	for _, nl := range namespaceLabels {
		for _, ol := range objectLabels {
			matches := sets.NewSet()
			for name, whs := range webhooks {
				for _, wh := range whs {
					if selectorMatches(wh.NamespaceSelector, nl) && selectorMatches(wh.ObjectSelector, ol) {
						matches.Insert(fmt.Sprintf("%v/%v", name, wh.Name))
					}
				}
			}
			if len(matches) > 1 {
				for match := range matches {
					others := matches.Difference(sets.NewSet(match))
					context.Report(webhookCol, msg.NewInvalidWebhook(resources[match],
						fmt.Sprintf("Webhook overlaps with others: %v. This may cause injection to occur twice.", others.UnsortedList())))
				}
			}
		}
	}

	// Next, check service references
	if a.SkipServiceCheck {
		return
	}
	for name, whs := range webhooks {
		for _, wh := range whs {
			if wh.ClientConfig.Service == nil {
				// it is an url, skip it
				continue
			}
			fname := resource.NewFullName(
				resource.Namespace(wh.ClientConfig.Service.Namespace),
				resource.LocalName(wh.ClientConfig.Service.Name))
			if !context.Exists(serviceCol, fname) {
				context.Report(webhookCol, msg.NewInvalidWebhook(resources[fmt.Sprintf("%v/%v", name, wh.Name)],
					fmt.Sprintf("Injector refers to a control plane service that does not exist: %v.", fname)))
			}
		}
	}
}

func isIstioWebhook(wh *v1.MutatingWebhookConfiguration) bool {
	for _, w := range wh.Webhooks {
		if strings.HasSuffix(w.Name, "istio.io") {
			return true
		}
	}
	return false
}

func extractRevisions(wh *v1.MutatingWebhookConfiguration) []string {
	revs := sets.NewSet()
	if r, f := wh.Labels[label.IoIstioRev.Name]; f {
		revs.Insert(r)
	}
	for _, webhook := range wh.Webhooks {
		if webhook.NamespaceSelector != nil {
			if r, f := webhook.NamespaceSelector.MatchLabels[label.IoIstioRev.Name]; f {
				revs.Insert(r)
			}
		}
		for _, ls := range webhook.NamespaceSelector.MatchExpressions {
			if ls.Key == label.IoIstioRev.Name {
				revs.Insert(ls.Values...)
			}
		}
		if webhook.ObjectSelector != nil {
			if r, f := webhook.ObjectSelector.MatchLabels[label.IoIstioRev.Name]; f {
				revs.Insert(r)
			}

			for _, ls := range webhook.ObjectSelector.MatchExpressions {
				if ls.Key == label.IoIstioRev.Name {
					revs.Insert(ls.Values...)
				}
			}
		}
	}
	return revs.UnsortedList()
}

func selectorMatches(selector *metav1.LabelSelector, labels klabels.Set) bool {
	// From webhook spec: "Default to the empty LabelSelector, which matches everything."
	if selector == nil {
		return true
	}
	s, err := metav1.LabelSelectorAsSelector(selector)
	if err != nil {
		return false
	}
	return s.Matches(labels)
}
<commit_msg>fix nil pointer panic (#36336)<commit_after>// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package webhook

import (
	"fmt"
	"strings"

	v1 "k8s.io/api/admissionregistration/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	klabels "k8s.io/apimachinery/pkg/labels"

	"istio.io/api/label"
	"istio.io/istio/pilot/pkg/util/sets"
	"istio.io/istio/pkg/config/analysis"
	"istio.io/istio/pkg/config/analysis/msg"
	"istio.io/istio/pkg/config/resource"
	"istio.io/istio/pkg/config/schema/collection"
	"istio.io/istio/pkg/config/schema/collections"
)

var (
	webhookCol = collections.K8SAdmissionregistrationK8SIoV1Mutatingwebhookconfigurations.Name()
	serviceCol = collections.K8SCoreV1Services.Name()
)

type Analyzer struct {
	SkipServiceCheck bool
}

var _ analysis.Analyzer = &Analyzer{}

func (a *Analyzer) Metadata() analysis.Metadata {
	return analysis.Metadata{
		Name:        "webhook.Analyzer",
		Description: "Checks the validity of Istio webhooks",
		Inputs: collection.Names{
			webhookCol,
			serviceCol,
		},
	}
}

func getNamespaceLabels() []klabels.Set {
	return []klabels.Set{
		{},
		{"istio-injection": "enabled"},
		{"istio-injection": "disabled"},
	}
}

func getObjectLabels() []klabels.Set {
	return []klabels.Set{
		{},
		{"sidecar.istio.io/inject": "true"},
		{"sidecar.istio.io/inject": "false"},
	}
}

func (a *Analyzer) Analyze(context analysis.Context) {
	// First, extract and index all webhooks we found
	webhooks := map[string][]v1.MutatingWebhook{}
	resources := map[string]*resource.Instance{}
	revisions := sets.NewSet()
	context.ForEach(webhookCol, func(resource *resource.Instance) bool {
		wh := resource.Message.(*v1.MutatingWebhookConfiguration)
		revs := extractRevisions(wh)
		if len(revs) == 0 && !isIstioWebhook(wh) {
			return true
		}
		webhooks[resource.Metadata.FullName.String()] = wh.Webhooks
		for _, h := range wh.Webhooks {
			resources[fmt.Sprintf("%v/%v", resource.Metadata.FullName.String(), h.Name)] = resource
		}
		revisions.Insert(revs...)
		return true
	})

	// Set up all relevant namespace and object selector permutations
	namespaceLabels := getNamespaceLabels()
	for rev := range revisions {
		for _, base := range getNamespaceLabels() {
			base[label.IoIstioRev.Name] = rev
			namespaceLabels = append(namespaceLabels, base)
		}
	}
	objectLabels := getObjectLabels()
	for rev := range revisions {
		for _, base := range getObjectLabels() {
			base[label.IoIstioRev.Name] = rev
			objectLabels = append(objectLabels, base)
		}
	}

	// For each permutation, we check which webhooks it matches. It must match exactly 0 or 1!
	for _, nl := range namespaceLabels {
		for _, ol := range objectLabels {
			matches := sets.NewSet()
			for name, whs := range webhooks {
				for _, wh := range whs {
					if selectorMatches(wh.NamespaceSelector, nl) && selectorMatches(wh.ObjectSelector, ol) {
						matches.Insert(fmt.Sprintf("%v/%v", name, wh.Name))
					}
				}
			}
			if len(matches) > 1 {
				for match := range matches {
					others := matches.Difference(sets.NewSet(match))
					context.Report(webhookCol, msg.NewInvalidWebhook(resources[match],
						fmt.Sprintf("Webhook overlaps with others: %v. This may cause injection to occur twice.", others.UnsortedList())))
				}
			}
		}
	}

	// Next, check service references
	if a.SkipServiceCheck {
		return
	}
	for name, whs := range webhooks {
		for _, wh := range whs {
			if wh.ClientConfig.Service == nil {
				// it is an url, skip it
				continue
			}
			fname := resource.NewFullName(
				resource.Namespace(wh.ClientConfig.Service.Namespace),
				resource.LocalName(wh.ClientConfig.Service.Name))
			if !context.Exists(serviceCol, fname) {
				context.Report(webhookCol, msg.NewInvalidWebhook(resources[fmt.Sprintf("%v/%v", name, wh.Name)],
					fmt.Sprintf("Injector refers to a control plane service that does not exist: %v.", fname)))
			}
		}
	}
}

func isIstioWebhook(wh *v1.MutatingWebhookConfiguration) bool {
	for _, w := range wh.Webhooks {
		if strings.HasSuffix(w.Name, "istio.io") {
			return true
		}
	}
	return false
}

func extractRevisions(wh *v1.MutatingWebhookConfiguration) []string {
	revs := sets.NewSet()
	if r, f := wh.Labels[label.IoIstioRev.Name]; f {
		revs.Insert(r)
	}
	for _, webhook := range wh.Webhooks {
		if webhook.NamespaceSelector != nil {
			if r, f := webhook.NamespaceSelector.MatchLabels[label.IoIstioRev.Name]; f {
				revs.Insert(r)
			}

			for _, ls := range webhook.NamespaceSelector.MatchExpressions {
				if ls.Key == label.IoIstioRev.Name {
					revs.Insert(ls.Values...)
				}
			}
		}
		if webhook.ObjectSelector != nil {
			if r, f := webhook.ObjectSelector.MatchLabels[label.IoIstioRev.Name]; f {
				revs.Insert(r)
			}

			for _, ls := range webhook.ObjectSelector.MatchExpressions {
				if ls.Key == label.IoIstioRev.Name {
					revs.Insert(ls.Values...)
				}
			}
		}
	}
	return revs.UnsortedList()
}

func selectorMatches(selector *metav1.LabelSelector, labels klabels.Set) bool {
	// From webhook spec: "Default to the empty LabelSelector, which matches everything."
	if selector == nil {
		return true
	}
	s, err := metav1.LabelSelectorAsSelector(selector)
	if err != nil {
		return false
	}
	return s.Matches(labels)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Jetstack cert-manager contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package install installs the API group, making it available as an option to
// all of the API encoding/decoding machinery.
package install

import (
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"

	"github.com/jetstack/cert-manager/pkg/internal/api/validation"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager"
	v1 "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1alpha2"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1alpha3"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1beta1"
	cmvalidation "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/validation"
	cmmetav1 "github.com/jetstack/cert-manager/pkg/internal/apis/meta/v1"
)

// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
	utilruntime.Must(certmanager.AddToScheme(scheme))
        // The first version in this list will be the default version used
	utilruntime.Must(v1.AddToScheme(scheme))
	utilruntime.Must(v1beta1.AddToScheme(scheme))
	utilruntime.Must(v1alpha3.AddToScheme(scheme))
	utilruntime.Must(v1alpha2.AddToScheme(scheme))

	utilruntime.Must(cmmetav1.AddToScheme(scheme))
}

// InstallValidation registers validation functions for the API group with a
// validation registry
func InstallValidation(registry *validation.Registry) {
	utilruntime.Must(cmvalidation.AddToValidationRegistry(registry))
}
<commit_msg>Update pkg/internal/apis/certmanager/install/install.go<commit_after>/*
Copyright 2019 The Jetstack cert-manager contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package install installs the API group, making it available as an option to
// all of the API encoding/decoding machinery.
package install

import (
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"

	"github.com/jetstack/cert-manager/pkg/internal/api/validation"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager"
	v1 "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1alpha2"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1alpha3"
	"github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/v1beta1"
	cmvalidation "github.com/jetstack/cert-manager/pkg/internal/apis/certmanager/validation"
	cmmetav1 "github.com/jetstack/cert-manager/pkg/internal/apis/meta/v1"
)

// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
	utilruntime.Must(certmanager.AddToScheme(scheme))
       // The first version in this list will be the default version used
	utilruntime.Must(v1.AddToScheme(scheme))
	utilruntime.Must(v1beta1.AddToScheme(scheme))
	utilruntime.Must(v1alpha3.AddToScheme(scheme))
	utilruntime.Must(v1alpha2.AddToScheme(scheme))

	utilruntime.Must(cmmetav1.AddToScheme(scheme))
}

// InstallValidation registers validation functions for the API group with a
// validation registry
func InstallValidation(registry *validation.Registry) {
	utilruntime.Must(cmvalidation.AddToValidationRegistry(registry))
}
<|endoftext|>


<commit_before>package crane

import "testing"

func TestIncludes(t *testing.T) {
	dependencies := Dependencies{
		all:         []string{"link", "volumesFrom", "net"},
		link:        []string{"link"},
		volumesFrom: []string{"volumesFrom"},
		net:         "net",
	}

	if !dependencies.includes("link") || !dependencies.includes("volumesFrom") || !dependencies.includes("net") {
		t.Errorf("Dependencies should have included link, volumesFrom and net")
	}
	if dependencies.includes("non-existant") {
		t.Errorf("Dependencies should not have included 'non-existant'")
	}
}

func TestIncludesAsKind(t *testing.T) {
	dependencies := Dependencies{
		all:         []string{"link", "volumesFrom", "net"},
		link:        []string{"link"},
		volumesFrom: []string{"volumesFrom"},
		net:         "net",
	}

	if !dependencies.includesAsKind("link", "all") {
		t.Errorf("false negative")
	}
	if !dependencies.includesAsKind("volumesFrom", "all") {
		t.Errorf("false negative")
	}
	if !dependencies.includesAsKind("net", "all") {
		t.Errorf("false negative")
	}

	if !dependencies.includesAsKind("link", "link") {
		t.Errorf("false negative")
	}
	if dependencies.includesAsKind("volumesFrom", "link") {
		t.Errorf("false positive")
	}
	if dependencies.includesAsKind("net", "link") {
		t.Errorf("false positive")
	}

	if dependencies.includesAsKind("link", "volumesFrom") {
		t.Errorf("false positive")
	}
	if !dependencies.includesAsKind("volumesFrom", "volumesFrom") {
		t.Errorf("false negative")
	}
	if dependencies.includesAsKind("net", "volumesFrom") {
		t.Errorf("false positive")
	}

	if dependencies.includesAsKind("link", "net") {
		t.Errorf("false positive")
	}
	if dependencies.includesAsKind("volumesFrom", "net") {
		t.Errorf("false positive")
	}
	if !dependencies.includesAsKind("net", "net") {
		t.Errorf("false negative")
	}
}

func TestForKind(t *testing.T) {
	dependencies := Dependencies{
		all:         []string{"link", "volumesFrom", "net"},
		link:        []string{"link"},
		volumesFrom: []string{"volumesFrom"},
		net:         "net",
	}

	if kindDeps := dependencies.forKind("all"); len(kindDeps) != 3 {
		t.Errorf("all dependencies expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("link"); len(kindDeps) != 1 || kindDeps[0] != "link" {
		t.Errorf("link expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("volumesFrom"); len(kindDeps) != 1 || kindDeps[0] != "volumesFrom" {
		t.Errorf("volumesFrom expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("net"); len(kindDeps) != 1 || kindDeps[0] != "net" {
		t.Errorf("net expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("foobar"); len(kindDeps) != 0 {
		t.Errorf("no dependencies expected but got %v", kindDeps)
	}
}

func TestSatisfied(t *testing.T) {
	var dependencies Dependencies

	dependencies = Dependencies{
		all: []string{"a"},
	}
	if dependencies.satisfied() {
		t.Errorf("Dependencies was not empty, but appeared to be satisfied")
	}

	dependencies = Dependencies{
		all: []string{},
	}
	if !dependencies.satisfied() {
		t.Errorf("Dependencies was empty, but appeared not to be satisfied")
	}
}
<commit_msg>Refactor includesAsKind test<commit_after>package crane

import "testing"

func TestIncludes(t *testing.T) {
	dependencies := Dependencies{
		all:         []string{"link", "volumesFrom", "net"},
		link:        []string{"link"},
		volumesFrom: []string{"volumesFrom"},
		net:         "net",
	}

	if !dependencies.includes("link") || !dependencies.includes("volumesFrom") || !dependencies.includes("net") {
		t.Errorf("Dependencies should have included link, volumesFrom and net")
	}
	if dependencies.includes("non-existant") {
		t.Errorf("Dependencies should not have included 'non-existant'")
	}
}

func TestIncludesAsKind(t *testing.T) {
	dependencies := Dependencies{
		all:         []string{"link", "volumesFrom", "net"},
		link:        []string{"link"},
		volumesFrom: []string{"volumesFrom"},
		net:         "net",
	}

	examples := []struct {
		needle   string
		kind     string
		expected bool
	}{
		{ // kind all
			needle:   "link",
			kind:     "all",
			expected: true,
		},
		{
			needle:   "volumesFrom",
			kind:     "all",
			expected: true,
		},
		{
			needle:   "net",
			kind:     "all",
			expected: true,
		},
		{ // kind link
			needle:   "link",
			kind:     "link",
			expected: true,
		},
		{
			needle:   "volumesFrom",
			kind:     "link",
			expected: false,
		},
		{
			needle:   "net",
			kind:     "link",
			expected: false,
		},
		{ // kind volumesFrom
			needle:   "link",
			kind:     "volumesFrom",
			expected: false,
		},
		{
			needle:   "volumesFrom",
			kind:     "volumesFrom",
			expected: true,
		},
		{
			needle:   "net",
			kind:     "volumesFrom",
			expected: false,
		},
		{ // kind net
			needle:   "link",
			kind:     "net",
			expected: false,
		},
		{
			needle:   "volumesFrom",
			kind:     "net",
			expected: false,
		},
		{
			needle:   "net",
			kind:     "net",
			expected: true,
		},
	}

	for _, example := range examples {
		actual := dependencies.includesAsKind(example.needle, example.kind)
		if actual != example.expected {
			t.Errorf("includesAsKind should have returned %v, got %v for %v, %v", example.expected, actual, example.needle, example.kind)
		}
	}

}

func TestForKind(t *testing.T) {
	dependencies := Dependencies{
		all:         []string{"link", "volumesFrom", "net"},
		link:        []string{"link"},
		volumesFrom: []string{"volumesFrom"},
		net:         "net",
	}

	if kindDeps := dependencies.forKind("all"); len(kindDeps) != 3 {
		t.Errorf("all dependencies expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("link"); len(kindDeps) != 1 || kindDeps[0] != "link" {
		t.Errorf("link expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("volumesFrom"); len(kindDeps) != 1 || kindDeps[0] != "volumesFrom" {
		t.Errorf("volumesFrom expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("net"); len(kindDeps) != 1 || kindDeps[0] != "net" {
		t.Errorf("net expected but got %v", kindDeps)
	}
	if kindDeps := dependencies.forKind("foobar"); len(kindDeps) != 0 {
		t.Errorf("no dependencies expected but got %v", kindDeps)
	}
}

func TestSatisfied(t *testing.T) {
	var dependencies Dependencies

	dependencies = Dependencies{
		all: []string{"a"},
	}
	if dependencies.satisfied() {
		t.Errorf("Dependencies was not empty, but appeared to be satisfied")
	}

	dependencies = Dependencies{
		all: []string{},
	}
	if !dependencies.satisfied() {
		t.Errorf("Dependencies was empty, but appeared not to be satisfied")
	}
}
<|endoftext|>