<commit_before>package plugins

import (
	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/logger"
	"github.com/bwmarrin/discordgo"
	rethink "github.com/gorethink/gorethink"
	"github.com/olebedev/when"
	"github.com/olebedev/when/rules/common"
	"github.com/olebedev/when/rules/en"
	"strings"
	"time"
)

type Reminders struct {
	parser *when.Parser
}

type DB_Reminders struct {
	Id        string        `gorethink:"id,omitempty"`
	UserID    string        `gorethink:"userid"`
	Reminders []DB_Reminder `gorethink:"reminders"`
}

type DB_Reminder struct {
	Message   string `gorethink:"message"`
	ChannelID string `gorethink:"channelID"`
	GuildID   string `gorethink:"guildID"`
	Timestamp int64  `gorethink:"timestamp"`
}

func (r *Reminders) Commands() []string {
	return []string{
		"remind",
		"rm",
		"reminders",
		"rms",
	}
}

func (r *Reminders) Init(session *discordgo.Session) {
	r.parser = when.New(nil)
	r.parser.Add(en.All...)
	r.parser.Add(common.All...)

	go func() {
		defer helpers.Recover()

		for {
			var reminderBucket []DB_Reminders
			cursor, err := rethink.Table("reminders").Run(helpers.GetDB())
			helpers.Relax(err)

			err = cursor.All(&reminderBucket)
			helpers.Relax(err)

			for _, reminders := range reminderBucket {
				changes := false

				// Downward loop for in-loop element removal
				for idx := len(reminders.Reminders) - 1; idx >= 0; idx-- {
					reminder := reminders.Reminders[idx]

					if reminder.Timestamp <= time.Now().Unix() {
						session.ChannelMessageSend(
							reminder.ChannelID,
							":alarm_clock: Ring! Ring! <@"+reminders.UserID+">\n"+
								"You wanted me to remind you to `"+reminder.Message+"` :slight_smile:",
						)

						reminders.Reminders = append(reminders.Reminders[:idx], reminders.Reminders[idx+1:]...)
						changes = true
					}
				}

				if changes {
					setReminders(reminders.UserID, reminders)
				}
			}

			time.Sleep(10 * time.Second)
		}
	}()

	logger.PLUGIN.L("reminders", "Started reminder loop (10s)")
}

func (r *Reminders) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	switch command {
	case "rm", "remind":
		channel, err := cache.Channel(msg.ChannelID)
		helpers.Relax(err)

		parts := strings.Split(content, " ")

		if len(parts) < 3 {
			session.ChannelMessageSend(msg.ChannelID, ":x: Please check if the format is correct")
			return
		}

		r, err := r.parser.Parse(content, time.Now())
		helpers.Relax(err)
		if r == nil {
			session.ChannelMessageSend(msg.ChannelID, ":x: Please check if the format is correct")
			return
		}

		reminders := getReminders(msg.Author.ID)
		reminders.Reminders = append(reminders.Reminders, DB_Reminder{
			Message:   strings.Replace(content, r.Text, "", 1),
			ChannelID: channel.ID,
			GuildID:   channel.GuildID,
			Timestamp: r.Time.Unix(),
		})
		setReminders(msg.Author.ID, reminders)

		session.ChannelMessageSend(msg.ChannelID, "Ok I'll remind you :ok_hand:")
		break

	case "rms", "reminders":
		reminders := getReminders(msg.Author.ID)
		embedFields := []*discordgo.MessageEmbedField{}

		for _, reminder := range reminders.Reminders {
			ts := time.Unix(reminder.Timestamp, 0)
			channel := "?"
			guild := "?"

			chanRef, err := session.Channel(reminder.ChannelID)
			if err == nil {
				channel = chanRef.Name
			}

			guildRef, err := session.Guild(reminder.GuildID)
			if err == nil {
				guild = guildRef.Name
			}

			embedFields = append(embedFields, &discordgo.MessageEmbedField{
				Inline: false,
				Name:   reminder.Message,
				Value:  "At " + ts.String() + " in #" + channel + " of " + guild,
			})
		}

		if len(embedFields) == 0 {
			session.ChannelMessageSend(msg.ChannelID, helpers.GetText("remiders.empty"))
			return
		}

		session.ChannelMessageSendEmbed(msg.ChannelID, &discordgo.MessageEmbed{
			Title:  "Pending reminders",
			Fields: embedFields,
			Color:  0x0FADED,
		})
		break
	}
}

func getReminders(uid string) DB_Reminders {
	var reminderBucket DB_Reminders
	listCursor, err := rethink.Table("reminders").Filter(
		rethink.Row.Field("userid").Eq(uid),
	).Run(helpers.GetDB())
	defer listCursor.Close()
	err = listCursor.One(&reminderBucket)

	// If user has no DB entries create an empty document
	if err == rethink.ErrEmptyResult {
		_, e := rethink.Table("reminders").Insert(DB_Reminders{
			UserID:    uid,
			Reminders: make([]DB_Reminder, 0),
		}).RunWrite(helpers.GetDB())

		// If the creation was successful read the document
		if e != nil {
			panic(e)
		} else {
			return getReminders(uid)
		}
	} else if err != nil {
		panic(err)
	}

	return reminderBucket
}

func setReminders(uid string, reminders DB_Reminders) {
	_, err := rethink.Table("reminders").Update(reminders).Run(helpers.GetDB())
	helpers.Relax(err)
}
<commit_msg>[reminders] send reminders via DM<commit_after>package plugins

import (
	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/logger"
	"github.com/bwmarrin/discordgo"
	rethink "github.com/gorethink/gorethink"
	"github.com/olebedev/when"
	"github.com/olebedev/when/rules/common"
	"github.com/olebedev/when/rules/en"
	"strings"
	"time"
)

type Reminders struct {
	parser *when.Parser
}

type DB_Reminders struct {
	Id        string        `gorethink:"id,omitempty"`
	UserID    string        `gorethink:"userid"`
	Reminders []DB_Reminder `gorethink:"reminders"`
}

type DB_Reminder struct {
	Message   string `gorethink:"message"`
	ChannelID string `gorethink:"channelID"`
	GuildID   string `gorethink:"guildID"`
	Timestamp int64  `gorethink:"timestamp"`
}

func (r *Reminders) Commands() []string {
	return []string{
		"remindme",
		"rm",
		"reminders",
		"rms",
	}
}

func (r *Reminders) Init(session *discordgo.Session) {
	r.parser = when.New(nil)
	r.parser.Add(en.All...)
	r.parser.Add(common.All...)

	go func() {
		defer helpers.Recover()

		for {
			var reminderBucket []DB_Reminders
			cursor, err := rethink.Table("reminders").Run(helpers.GetDB())
			helpers.Relax(err)

			err = cursor.All(&reminderBucket)
			helpers.Relax(err)

			for _, reminders := range reminderBucket {
				changes := false

				// Downward loop for in-loop element removal
				for idx := len(reminders.Reminders) - 1; idx >= 0; idx-- {
					reminder := reminders.Reminders[idx]

					if reminder.Timestamp <= time.Now().Unix() {
						dmChannel, err := session.UserChannelCreate(reminders.UserID)
						helpers.Relax(err)

						session.ChannelMessageSend(
							dmChannel.ID,
							":alarm_clock: You wanted me to remind you about this:\n"+
								"```"+reminder.Message+"```",
						)

						reminders.Reminders = append(reminders.Reminders[:idx], reminders.Reminders[idx+1:]...)
						changes = true
					}
				}

				if changes {
					setReminders(reminders.UserID, reminders)
				}
			}

			time.Sleep(10 * time.Second)
		}
	}()

	logger.PLUGIN.L("reminders", "Started reminder loop (10s)")
}

func (r *Reminders) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
	switch command {
	case "rm", "remind", "remindme":
		channel, err := cache.Channel(msg.ChannelID)
		helpers.Relax(err)

		parts := strings.Split(content, " ")

		if len(parts) < 3 {
			session.ChannelMessageSend(msg.ChannelID, ":x: Please check if the format is correct")
			return
		}

		r, err := r.parser.Parse(content, time.Now())
		helpers.Relax(err)
		if r == nil {
			session.ChannelMessageSend(msg.ChannelID, ":x: Please check if the format is correct")
			return
		}

		reminders := getReminders(msg.Author.ID)
		reminders.Reminders = append(reminders.Reminders, DB_Reminder{
			Message:   strings.Replace(content, r.Text, "", 1),
			ChannelID: channel.ID,
			GuildID:   channel.GuildID,
			Timestamp: r.Time.Unix(),
		})
		setReminders(msg.Author.ID, reminders)

		session.ChannelMessageSend(msg.ChannelID, "Ok I'll remind you :ok_hand:")
		break

	case "rms", "reminders":
		reminders := getReminders(msg.Author.ID)
		embedFields := []*discordgo.MessageEmbedField{}

		for _, reminder := range reminders.Reminders {
			ts := time.Unix(reminder.Timestamp, 0)
			channel := "?"
			guild := "?"

			chanRef, err := session.Channel(reminder.ChannelID)
			if err == nil {
				channel = chanRef.Name
			}

			guildRef, err := session.Guild(reminder.GuildID)
			if err == nil {
				guild = guildRef.Name
			}

			embedFields = append(embedFields, &discordgo.MessageEmbedField{
				Inline: false,
				Name:   reminder.Message,
				Value:  "At " + ts.String() + " in #" + channel + " of " + guild,
			})
		}

		if len(embedFields) == 0 {
			session.ChannelMessageSend(msg.ChannelID, helpers.GetText("remiders.empty"))
			return
		}

		session.ChannelMessageSendEmbed(msg.ChannelID, &discordgo.MessageEmbed{
			Title:  "Pending reminders",
			Fields: embedFields,
			Color:  0x0FADED,
		})
		break
	}
}

func getReminders(uid string) DB_Reminders {
	var reminderBucket DB_Reminders
	listCursor, err := rethink.Table("reminders").Filter(
		rethink.Row.Field("userid").Eq(uid),
	).Run(helpers.GetDB())
	defer listCursor.Close()
	err = listCursor.One(&reminderBucket)

	// If user has no DB entries create an empty document
	if err == rethink.ErrEmptyResult {
		_, e := rethink.Table("reminders").Insert(DB_Reminders{
			UserID:    uid,
			Reminders: make([]DB_Reminder, 0),
		}).RunWrite(helpers.GetDB())

		// If the creation was successful read the document
		if e != nil {
			panic(e)
		} else {
			return getReminders(uid)
		}
	} else if err != nil {
		panic(err)
	}

	return reminderBucket
}

func setReminders(uid string, reminders DB_Reminders) {
	_, err := rethink.Table("reminders").Update(reminders).Run(helpers.GetDB())
	helpers.Relax(err)
}
<|endoftext|>


<commit_before>package models

import (
	"socialapi/workers/common/runner"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestChannelMessageListFetchMessageChannels(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while fethcing channel message of a message", t, func() {
		Convey("channels should be valid", func() {
			c1 := createNewChannelWithTest()
			So(c1.Create(), ShouldBeNil)

			c2 := createNewChannelWithTest()
			So(c2.Create(), ShouldBeNil)

			c3 := createNewChannelWithTest()
			So(c3.Create(), ShouldBeNil)

			cm := NewChannelMessage()
			cm.Body = "gel beri abi"
			cm.AccountId = c1.CreatorId
			cm.InitialChannelId = c1.Id
			So(cm.Create(), ShouldBeNil)

			// add to first channel
			_, err := c1.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			// add to second channel
			_, err = c2.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			// add to 3rd channel
			_, err = c3.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			channels, err := NewChannelMessageList().FetchMessageChannels(cm.Id)
			So(err, ShouldBeNil)
			So(len(channels), ShouldEqual, 3)

			So(c1.Name, ShouldEqual, channels[0].Name)
			So(c2.Name, ShouldEqual, channels[1].Name)
			So(c3.Name, ShouldEqual, channels[2].Name)

			So(c1.GroupName, ShouldEqual, channels[0].GroupName)
			So(c2.GroupName, ShouldEqual, channels[1].GroupName)
			So(c3.GroupName, ShouldEqual, channels[2].GroupName)
		})
	})
}

func TestChannelMessageListCount(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while counting messages", t, func() {
		Convey("it should error if channel id is not set", func() {
			cml := NewChannelMessageList()

			c, err := cml.Count(0)
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
			So(c, ShouldEqual, 0)
		})

		Convey("it should not error if message in the channel", func() {
			// create message
			cm := createMessageWithTest()
			So(cm.Create(), ShouldBeNil)

			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, err := c.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id

			cnt, err := cml.Count(cml.ChannelId)
			So(err, ShouldBeNil)
			So(cnt, ShouldEqual, 1)
		})

		Convey("it should not count message if account of message is troll ", func() {
			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			// create account as troll
			acc1 := createAccountWithTest()
			acc1.MarkAsTroll()

			acc2 := createAccountWithTest()

			// create message that creator is troll
			msg := createMessageWithTest()
			msg.AccountId = acc1.Id
			So(msg.Create(), ShouldBeNil)

			msg2 := createMessageWithTest()
			msg2.AccountId = acc2.Id
			So(msg2.Create(), ShouldBeNil)

			// add message to the channel
			// account is troll !!
			_, err := c.AddMessage(msg.Id)
			So(err, ShouldBeNil)

			_, erre := c.AddMessage(msg2.Id)
			So(erre, ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id

			// there is 2 message in the channel
			// but one of the account of message is troll so;
			// Count will not count this message
			// messages of troll is not valid to count
			cnt, err := cml.Count(cml.ChannelId)
			So(err, ShouldBeNil)
			So(cnt, ShouldEqual, 1)
		})
	})
}

func TestChannelMessageListisExempt(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while testing is exempt", t, func() {
		Convey("it should have error if message id is not set ", func() {
			cml := NewChannelMessageList()

			is, err := cml.isExempt()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrMessageIdIsNotSet)
			So(is, ShouldEqual, false)
		})

		Convey("it should return true is channel is exempt", func() {
			// create account as troll
			acc := createAccountWithTest()
			acc.MarkAsTroll()

			// create channel
			c := createNewChannelWithTest()
			c.CreatorId = acc.Id

			// create message
			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id
			cml.MessageId = msg.Id

			is, err := cml.isExempt()
			So(err, ShouldBeNil)
			So(is, ShouldEqual, true)
		})

		Convey("it should return false is channel is not exempt", func() {
			// create account as not troll
			acc := createAccountWithTest()
			acc.IsTroll = false

			// create channel
			c := createNewChannelWithTest()
			c.CreatorId = acc.Id

			// create message
			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id
			cml.MessageId = msg.Id

			is, err := cml.isExempt()
			So(err, ShouldBeNil)
			So(is, ShouldEqual, false)
		})
	})
}

func TestChannelMessageListMarkIfExempt(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while marking if channel is exempt", t, func() {
		Convey("it should have error if message id is not set", func() {
			cml := NewChannelMessageList()

			err := cml.MarkIfExempt()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrMessageIdIsNotSet)
		})

		Convey("it should mark as exempt if channel is exempt", func() {
			// create account as troll
			acc := createAccountWithTest()
			acc.IsTroll = true

			// create channel
			c := createNewChannelWithTest()
			c.CreatorId = acc.Id

			// create message
			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id
			cml.MessageId = msg.Id

			err := cml.MarkIfExempt()
			So(err, ShouldBeNil)
		})

	})
}
<commit_msg>social/tests: error checking is added to tests<commit_after>package models

import (
	"socialapi/workers/common/runner"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestChannelMessageListFetchMessageChannels(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while fethcing channel message of a message", t, func() {
		Convey("channels should be valid", func() {
			c1 := createNewChannelWithTest()
			So(c1.Create(), ShouldBeNil)

			c2 := createNewChannelWithTest()
			So(c2.Create(), ShouldBeNil)

			c3 := createNewChannelWithTest()
			So(c3.Create(), ShouldBeNil)

			cm := NewChannelMessage()
			cm.Body = "gel beri abi"
			cm.AccountId = c1.CreatorId
			cm.InitialChannelId = c1.Id
			So(cm.Create(), ShouldBeNil)

			// add to first channel
			_, err := c1.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			// add to second channel
			_, err = c2.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			// add to 3rd channel
			_, err = c3.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			channels, err := NewChannelMessageList().FetchMessageChannels(cm.Id)
			So(err, ShouldBeNil)
			So(len(channels), ShouldEqual, 3)

			So(c1.Name, ShouldEqual, channels[0].Name)
			So(c2.Name, ShouldEqual, channels[1].Name)
			So(c3.Name, ShouldEqual, channels[2].Name)

			So(c1.GroupName, ShouldEqual, channels[0].GroupName)
			So(c2.GroupName, ShouldEqual, channels[1].GroupName)
			So(c3.GroupName, ShouldEqual, channels[2].GroupName)
		})
	})
}

func TestChannelMessageListCount(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while counting messages", t, func() {
		Convey("it should error if channel id is not set", func() {
			cml := NewChannelMessageList()

			c, err := cml.Count(0)
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
			So(c, ShouldEqual, 0)
		})

		Convey("it should not error if message in the channel", func() {
			// create message
			cm := createMessageWithTest()
			So(cm.Create(), ShouldBeNil)

			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, err := c.AddMessage(cm.Id)
			So(err, ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id

			cnt, err := cml.Count(cml.ChannelId)
			So(err, ShouldBeNil)
			So(cnt, ShouldEqual, 1)
		})

		Convey("it should not count message if account of message is troll ", func() {
			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			// create account as troll
			acc1 := createAccountWithTest()
			err := acc1.MarkAsTroll()
			So(err, ShouldBeNil)

			acc2 := createAccountWithTest()

			// create message that creator is troll
			msg := createMessageWithTest()
			msg.AccountId = acc1.Id
			So(msg.Create(), ShouldBeNil)

			msg2 := createMessageWithTest()
			msg2.AccountId = acc2.Id
			So(msg2.Create(), ShouldBeNil)

			// add message to the channel
			// account is troll !!
			_, erro := c.AddMessage(msg.Id)
			So(erro, ShouldBeNil)

			_, erre := c.AddMessage(msg2.Id)
			So(erre, ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id

			// there is 2 message in the channel
			// but one of the account of message is troll so;
			// Count will not count this message
			// messages of troll is not valid to count
			cnt, err := cml.Count(cml.ChannelId)
			So(err, ShouldBeNil)
			So(cnt, ShouldEqual, 1)
		})
	})
}

func TestChannelMessageListisExempt(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while testing is exempt", t, func() {
		Convey("it should have error if message id is not set ", func() {
			cml := NewChannelMessageList()

			is, err := cml.isExempt()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrMessageIdIsNotSet)
			So(is, ShouldEqual, false)
		})

		Convey("it should return true is channel is exempt", func() {
			// create account as troll
			acc := createAccountWithTest()
			err := acc.MarkAsTroll()
			So(err, ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			c.CreatorId = acc.Id

			// create message
			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id
			cml.MessageId = msg.Id

			is, err := cml.isExempt()
			So(err, ShouldBeNil)
			So(is, ShouldEqual, true)
		})

		Convey("it should return false is channel is not exempt", func() {
			// create account as not troll
			acc := createAccountWithTest()
			acc.IsTroll = false

			// create channel
			c := createNewChannelWithTest()
			c.CreatorId = acc.Id

			// create message
			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id
			cml.MessageId = msg.Id

			is, err := cml.isExempt()
			So(err, ShouldBeNil)
			So(is, ShouldEqual, false)
		})
	})
}

func TestChannelMessageListMarkIfExempt(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("while marking if channel is exempt", t, func() {
		Convey("it should have error if message id is not set", func() {
			cml := NewChannelMessageList()

			err := cml.MarkIfExempt()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrMessageIdIsNotSet)
		})

		Convey("it should mark as exempt if channel is exempt", func() {
			// create account as troll
			acc := createAccountWithTest()
			err := acc.MarkAsTroll()
			So(err, ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			c.CreatorId = acc.Id

			// create message
			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			cml := NewChannelMessageList()
			cml.ChannelId = c.Id
			cml.MessageId = msg.Id

			errr := cml.MarkIfExempt()
			So(errr, ShouldBeNil)
		})

	})
}
<|endoftext|>


<commit_before>package models

import (
	"socialapi/workers/common/runner"
	"testing"
	"time"

	"github.com/koding/bongo"
	. "github.com/smartystreets/goconvey/convey"
)

func TestChannelparticipantNewChannelParticipant(t *testing.T) {
	Convey("while testing New Channel Participant", t, func() {
		Convey("Function call should return ChannelParticipant", func() {
			So(NewChannelParticipant(), ShouldNotBeNil)
		})
	})
}

func TestChannelparticipantGetId(t *testing.T) {
	Convey("While testing get id", t, func() {
		Convey("Initialized struct", func() {
			Convey("should return given id", func() {
				c := ChannelParticipant{Id: 45}
				So(c.GetId(), ShouldEqual, 45)
			})
		})
		Convey("Uninitialized struct", func() {
			Convey("should return 0", func() {
				So(NewChannelParticipant().GetId(), ShouldEqual, 0)
			})
			So(NewChannelParticipant, ShouldNotBeNil)
		})
	})
}

func TestChannelParticipantTableName(t *testing.T) {
	Convey("While getting table name", t, func() {
		Convey("table names should match", func() {
			c := ChannelParticipant{}
			So(c.TableName(), ShouldEqual, "api.channel_participant")
		})
	})
}

func TestChannelParticipantBeforeUpdate(t *testing.T) {
	Convey("While testing before update", t, func() {
		Convey("LastSeenAt should be updated", func() {
			lastSeenAt := time.Now().UTC()

			account, err := createAccount()
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)
			So(account.Id, ShouldNotEqual, 0)

			c := NewChannelParticipant()
			c.LastSeenAt = lastSeenAt
			c.AccountId = account.Id

			// call before update
			err = c.BeforeUpdate()

			// make sure err is nil
			So(err, ShouldBeNil)
			// check preset last seen at is not same after calling
			// before update function
			So(c.LastSeenAt, ShouldNotEqual, lastSeenAt)
		})
	})
}

func TestChannelParticipantIsParticipant(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While testing is participant", t, func() {
		Convey("it should have channel id", func() {
			cp := NewChannelParticipant()

			ip, err := cp.IsParticipant(1123)
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
			So(ip, ShouldEqual, false)
		})

		Convey("it should return false if account is not exist", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 120

			ip, err := cp.IsParticipant(112345)
			So(err, ShouldBeNil)
			So(ip, ShouldEqual, false)
		})

		Convey("it should not have error if account is exist", func() {
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)
			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id
			So(cp.Create(), ShouldBeNil)

			_, err := c.AddParticipant(acc.Id)

			ip, err := cp.IsParticipant(acc.Id)
			So(err, ShouldBeNil)
			So(ip, ShouldEqual, true)
			So(cp.StatusConstant, ShouldEqual, ChannelParticipant_STATUS_ACTIVE)
		})
	})
}

func TestChannelParticipantFetchParticipantCount(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While fetching participant count", t, func() {
		Convey("it should have channel id", func() {
			cp := NewChannelParticipant()

			_, err := cp.FetchParticipantCount()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
		})

		Convey("it should have zero value if account is not exist in the channel", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 2442

			fp, err := cp.FetchParticipantCount()
			So(err, ShouldBeNil)
			So(fp, ShouldEqual, 0)
		})

		Convey("it should return participant count successfully", func() {
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)
			acc1 := createAccountWithTest()
			So(acc1.Create(), ShouldBeNil)
			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id
			So(cp.Create(), ShouldBeNil)

			c.AddParticipant(acc.Id)
			c.AddParticipant(acc1.Id)

			ip, err := cp.FetchParticipantCount()
			So(err, ShouldBeNil)
			So(ip, ShouldNotEqual, 2)

		})
	})

}

func TestChannelParticipantgetAccountId(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While getting account id", t, func() {
		Convey("it should have channel id", func() {
			cp := NewChannelParticipant()

			gai, err := cp.getAccountId()
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "couldnt find accountId from content")
			So(gai, ShouldEqual, 0)
		})

		Convey("it should have error if account is not exist", func() {
			cp := NewChannelParticipant()
			cp.Id = 123145

			gai, err := cp.getAccountId()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, bongo.RecordNotFound)
			So(gai, ShouldEqual, 0)
		})

		Convey("it should return account id if account is exist", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			cp := NewChannelParticipant()
			cp.Id = 1201
			cp.AccountId = acc.Id

			gai, err := cp.getAccountId()
			So(err, ShouldBeNil)
			So(gai, ShouldEqual, acc.Id)
		})

	})

}

func TestChannelParticipantFetchParticipant(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While fetching participant", t, func() {
		Convey("it should have error if channel id is not set", func() {
			cp := NewChannelParticipant()

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
		})

		Convey("it should have error if account id is not set", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 1453

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrAccountIdIsNotSet)
		})

		Convey("it should have error if account is not exist", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 1453
			cp.AccountId = 1454

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, bongo.RecordNotFound)
		})

		Convey("it should not have any error if account is exist in channel", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, errr := c.AddParticipant(acc.Id)
			So(errr, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			err := cp.FetchParticipant()
			So(err, ShouldBeNil)
		})
	})
}

func TestChannelParticipantFetcActiveParticipant(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While fetching active participant", t, func() {
		Convey("it should have error if channel id is not set", func() {
			cp := NewChannelParticipant()

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
		})

		Convey("it should return active participant successfully", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, errr := c.AddParticipant(acc.Id)
			So(errr, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			err := cp.FetchActiveParticipant()
			So(err, ShouldBeNil)
		})

		Convey("it should have error if account is not exist in channel", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, errr := c.AddParticipant(acc.Id)
			So(errr, ShouldBeNil)

			erro := c.RemoveParticipant(acc.Id)
			So(erro, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			err := cp.FetchActiveParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, bongo.RecordNotFound)
		})

	})
}

func TestChannelParticipantMarkIfExempt(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While marking if participant is exempt", t, func() {
		Convey("it should have error if channel id is not set", func() {
			// create account
			acc := createAccountWithTest()
			acc.IsTroll = true
			So(acc.Update(), ShouldBeNil)

			c := createNewChannelWithTest()
			c.CreatorId = acc.Id
			So(c.Create(), ShouldBeNil)

			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			_, errs := c.AddMessage(msg.Id)
			So(errs, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			_, erro := c.AddParticipant(acc.Id)
			So(erro, ShouldBeNil)

			err := cp.MarkIfExempt()
			So(err, ShouldBeNil)

		})

	})
}
<commit_msg>social/tests:MarkIfExempt tests are added for channelparticipant<commit_after>package models

import (
	"socialapi/workers/common/runner"
	"testing"
	"time"

	"github.com/koding/bongo"
	. "github.com/smartystreets/goconvey/convey"
)

func TestChannelparticipantNewChannelParticipant(t *testing.T) {
	Convey("while testing New Channel Participant", t, func() {
		Convey("Function call should return ChannelParticipant", func() {
			So(NewChannelParticipant(), ShouldNotBeNil)
		})
	})
}

func TestChannelparticipantGetId(t *testing.T) {
	Convey("While testing get id", t, func() {
		Convey("Initialized struct", func() {
			Convey("should return given id", func() {
				c := ChannelParticipant{Id: 45}
				So(c.GetId(), ShouldEqual, 45)
			})
		})
		Convey("Uninitialized struct", func() {
			Convey("should return 0", func() {
				So(NewChannelParticipant().GetId(), ShouldEqual, 0)
			})
			So(NewChannelParticipant, ShouldNotBeNil)
		})
	})
}

func TestChannelParticipantTableName(t *testing.T) {
	Convey("While getting table name", t, func() {
		Convey("table names should match", func() {
			c := ChannelParticipant{}
			So(c.TableName(), ShouldEqual, "api.channel_participant")
		})
	})
}

func TestChannelParticipantBeforeUpdate(t *testing.T) {
	Convey("While testing before update", t, func() {
		Convey("LastSeenAt should be updated", func() {
			lastSeenAt := time.Now().UTC()

			account, err := createAccount()
			So(err, ShouldBeNil)
			So(account, ShouldNotBeNil)
			So(account.Id, ShouldNotEqual, 0)

			c := NewChannelParticipant()
			c.LastSeenAt = lastSeenAt
			c.AccountId = account.Id

			// call before update
			err = c.BeforeUpdate()

			// make sure err is nil
			So(err, ShouldBeNil)
			// check preset last seen at is not same after calling
			// before update function
			So(c.LastSeenAt, ShouldNotEqual, lastSeenAt)
		})
	})
}

func TestChannelParticipantIsParticipant(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While testing is participant", t, func() {
		Convey("it should have channel id", func() {
			cp := NewChannelParticipant()

			ip, err := cp.IsParticipant(1123)
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
			So(ip, ShouldEqual, false)
		})

		Convey("it should return false if account is not exist", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 120

			ip, err := cp.IsParticipant(112345)
			So(err, ShouldBeNil)
			So(ip, ShouldEqual, false)
		})

		Convey("it should not have error if account is exist", func() {
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)
			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id
			So(cp.Create(), ShouldBeNil)

			_, err := c.AddParticipant(acc.Id)

			ip, err := cp.IsParticipant(acc.Id)
			So(err, ShouldBeNil)
			So(ip, ShouldEqual, true)
			So(cp.StatusConstant, ShouldEqual, ChannelParticipant_STATUS_ACTIVE)
		})
	})
}

func TestChannelParticipantFetchParticipantCount(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While fetching participant count", t, func() {
		Convey("it should have channel id", func() {
			cp := NewChannelParticipant()

			_, err := cp.FetchParticipantCount()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
		})

		Convey("it should have zero value if account is not exist in the channel", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 2442

			fp, err := cp.FetchParticipantCount()
			So(err, ShouldBeNil)
			So(fp, ShouldEqual, 0)
		})

		Convey("it should return participant count successfully", func() {
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)
			acc1 := createAccountWithTest()
			So(acc1.Create(), ShouldBeNil)
			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id
			So(cp.Create(), ShouldBeNil)

			c.AddParticipant(acc.Id)
			c.AddParticipant(acc1.Id)

			ip, err := cp.FetchParticipantCount()
			So(err, ShouldBeNil)
			So(ip, ShouldNotEqual, 2)

		})
	})

}

func TestChannelParticipantgetAccountId(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While getting account id", t, func() {
		Convey("it should have channel id", func() {
			cp := NewChannelParticipant()

			gai, err := cp.getAccountId()
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "couldnt find accountId from content")
			So(gai, ShouldEqual, 0)
		})

		Convey("it should have error if account is not exist", func() {
			cp := NewChannelParticipant()
			cp.Id = 123145

			gai, err := cp.getAccountId()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, bongo.RecordNotFound)
			So(gai, ShouldEqual, 0)
		})

		Convey("it should return account id if account is exist", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			cp := NewChannelParticipant()
			cp.Id = 1201
			cp.AccountId = acc.Id

			gai, err := cp.getAccountId()
			So(err, ShouldBeNil)
			So(gai, ShouldEqual, acc.Id)
		})

	})

}

func TestChannelParticipantFetchParticipant(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While fetching participant", t, func() {
		Convey("it should have error if channel id is not set", func() {
			cp := NewChannelParticipant()

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
		})

		Convey("it should have error if account id is not set", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 1453

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrAccountIdIsNotSet)
		})

		Convey("it should have error if account is not exist", func() {
			cp := NewChannelParticipant()
			cp.ChannelId = 1453
			cp.AccountId = 1454

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, bongo.RecordNotFound)
		})

		Convey("it should not have any error if account is exist in channel", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, errr := c.AddParticipant(acc.Id)
			So(errr, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			err := cp.FetchParticipant()
			So(err, ShouldBeNil)
		})
	})
}

func TestChannelParticipantFetcActiveParticipant(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While fetching active participant", t, func() {
		Convey("it should have error if channel id is not set", func() {
			cp := NewChannelParticipant()

			err := cp.FetchParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, ErrChannelIdIsNotSet)
		})

		Convey("it should return active participant successfully", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, errr := c.AddParticipant(acc.Id)
			So(errr, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			err := cp.FetchActiveParticipant()
			So(err, ShouldBeNil)
		})

		Convey("it should have error if account is not exist in channel", func() {
			// create account
			acc := createAccountWithTest()
			So(acc.Create(), ShouldBeNil)

			// create channel
			c := createNewChannelWithTest()
			So(c.Create(), ShouldBeNil)

			_, errr := c.AddParticipant(acc.Id)
			So(errr, ShouldBeNil)

			erro := c.RemoveParticipant(acc.Id)
			So(erro, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			err := cp.FetchActiveParticipant()
			So(err, ShouldNotBeNil)
			So(err, ShouldEqual, bongo.RecordNotFound)
		})

	})
}

func TestChannelParticipantMarkIfExempt(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldnt start bongo %s", err.Error())
	}
	defer r.Close()

	Convey("While marking if participant is exempt", t, func() {
		Convey("it should be nil if participant is already exempt", func() {
			// create account
			acc := createAccountWithTest()
			acc.IsTroll = true
			So(acc.Update(), ShouldBeNil)

			c := createNewChannelWithTest()
			c.CreatorId = acc.Id
			So(c.Create(), ShouldBeNil)

			msg := createMessageWithTest()
			msg.AccountId = acc.Id
			So(msg.Create(), ShouldBeNil)

			_, errs := c.AddMessage(msg.Id)
			So(errs, ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id
			cp.AccountId = acc.Id

			_, erro := c.AddParticipant(acc.Id)
			So(erro, ShouldBeNil)

			err := cp.MarkIfExempt()
			So(err, ShouldBeNil)
		})

		Convey("it should be nil if participant is already exempt", func() {
			// create account
			acc := createAccountWithTest()
			acc.IsTroll = false
			So(acc.Update(), ShouldBeNil)

			c := createNewChannelWithTest()
			c.CreatorId = acc.Id
			So(c.Create(), ShouldBeNil)

			cp := NewChannelParticipant()
			cp.ChannelId = c.Id

			err := cp.MarkIfExempt()
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "couldnt find accountId from content")
		})

	})
}
<|endoftext|>


<commit_before>package main

import (
	"socialapi/models"
	"socialapi/rest"
	"socialapi/workers/common/tests"
	"strings"
	"testing"
	"time"

	"github.com/cihangir/nisql"
	"github.com/koding/bongo"
	"github.com/koding/runner"

	. "github.com/smartystreets/goconvey/convey"
)

func TestChannelParticipantOperations(t *testing.T) {
	tests.WithRunner(t, func(r *runner.Runner) {
		Convey("while testing channel participants", t, func() {
			Convey("First Create Users and initiate conversation", func() {
				ownerAccount, groupChannel, groupName := models.CreateRandomGroupDataWithChecks()

				ownerSes, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
				So(err, ShouldBeNil)
				So(ownerSes, ShouldNotBeNil)

				secondAccount, err := models.CreateAccountInBothDbs()
				tests.ResultedWithNoErrorCheck(secondAccount, err)
				_, err = groupChannel.AddParticipant(secondAccount.Id)
				So(err, ShouldBeNil)

				thirdAccount, err := models.CreateAccountInBothDbs()
				tests.ResultedWithNoErrorCheck(thirdAccount, err)
				_, err = groupChannel.AddParticipant(thirdAccount.Id)
				So(err, ShouldBeNil)

				forthAccount, err := models.CreateAccountInBothDbs()
				tests.ResultedWithNoErrorCheck(forthAccount, err)
				_, err = groupChannel.AddParticipant(forthAccount.Id)
				So(err, ShouldBeNil)

				devrim, err := models.CreateAccountInBothDbsWithNick("devrim")
				tests.ResultedWithNoErrorCheck(devrim, err)
				_, err = groupChannel.AddParticipant(devrim.Id)
				So(err, ShouldBeNil)

				ses, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
				tests.ResultedWithNoErrorCheck(ses, err)

				secondSes, err := models.FetchOrCreateSession(secondAccount.Nick, groupName)
				tests.ResultedWithNoErrorCheck(secondSes, err)

				pmr := models.ChannelRequest{}

				pmr.AccountId = ownerAccount.Id

				pmr.Body = "new conversation"
				pmr.GroupName = groupName
				pmr.Recipients = []string{"devrim"}

				channelContainer, err := rest.SendPrivateChannelRequest(pmr, ownerSes.ClientId)
				So(err, ShouldBeNil)
				So(channelContainer, ShouldNotBeNil)

				Convey("First user should be able to add second and third users to conversation", func() {
					_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id, thirdAccount.Id)
					So(err, ShouldBeNil)
					participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
					So(err, ShouldBeNil)
					So(participants, ShouldNotBeNil)
					// it is four because first user is "devrim" here
					So(len(participants), ShouldEqual, 4)

					Convey("First user should not be able to re-add second participant", func() {
						_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 4)
					})

					Convey("Second user should be able to leave conversation", func() {
						// token of account -> secondAccount
						_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 3)

						Convey("A user who is not participant of a conversation should not be able to add another user to the conversation", func() {
							_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, forthAccount.Id)
							So(err, ShouldNotBeNil)

							participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
							So(err, ShouldBeNil)
							So(participants, ShouldNotBeNil)
							So(len(participants), ShouldEqual, 3)
						})
					})

					Convey("Channel owner should be able to kick another conversation participant", func() {
						_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 3)
					})

					Convey("when a user is blocked", func() {
						_, err = rest.BlockChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						Convey("it should not be in channel participant list", func() {
							participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
							So(err, ShouldBeNil)
							So(participants, ShouldNotBeNil)
							So(len(participants), ShouldEqual, 3)
						})

						Convey("should not be able to add it back", func() {
							_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
							So(err, ShouldNotBeNil)
						})

						Convey("should be able to unblock", func() {
							_, err = rest.UnblockChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
							So(err, ShouldBeNil)

							Convey("it should not be in channel participant list still", func() {
								participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
								So(err, ShouldBeNil)
								So(participants, ShouldNotBeNil)
								So(len(participants), ShouldEqual, 3)
							})

							Convey("when we add the same user as participant", func() {
								_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id, thirdAccount.Id)
								So(err, ShouldBeNil)

								Convey("it should be in channel participant list", func() {
									participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
									So(err, ShouldBeNil)
									So(participants, ShouldNotBeNil)
									So(len(participants), ShouldEqual, 4)
								})
							})
						})
					})

					Convey("Second user should not be able to kick another conversation participant", func() {
						_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, thirdAccount.Id)
						So(err, ShouldNotBeNil)
					})

				})
				Convey("First user should be able to invite second user", func() {
					_, err = rest.InviteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
					So(err, ShouldBeNil)
					participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
					So(err, ShouldBeNil)
					So(participants, ShouldNotBeNil)
					// it is four because first user is "devrim" here
					So(len(participants), ShouldEqual, 2)

					Convey("Second user should be able to reject invitation", func() {
						ses, err := models.FetchOrCreateSession(secondAccount.Nick, groupName)
						So(err, ShouldBeNil)
						So(ses, ShouldNotBeNil)

						err = rest.RejectInvitation(channelContainer.Channel.Id, ses.ClientId)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 2)
					})

					Convey("Second user should be able to accept invitation", func() {
						ses, err := models.FetchOrCreateSession(secondAccount.Nick, groupName)
						So(err, ShouldBeNil)
						So(ses, ShouldNotBeNil)

						err = rest.AcceptInvitation(channelContainer.Channel.Id, ses.ClientId)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 3)
					})
				})

				// TODO Until we find a better way for handling async stuff, this test is skipped. Instead of sleep, we should use some
				// timeouts for testing these kind of stuff.
				SkipConvey("All private messages must be deleted when all participant users leave the channel", func() {
					account := models.NewAccount()
					err = account.ByNick("devrim")
					So(err, ShouldBeNil)

					_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ses.ClientId, account.Id)
					So(err, ShouldBeNil)

					_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, ownerAccount.Id)
					So(err, ShouldBeNil)

					time.Sleep(1 * time.Second)

					testChannel := models.NewChannel()
					err := testChannel.ById(channelContainer.Channel.Id)
					So(err, ShouldEqual, bongo.RecordNotFound)

					testChannelList := models.NewChannelMessageList()
					err = bongo.B.Unscoped().Where("channel_id = ?", channelContainer.Channel.Id).Find(testChannelList).Error
					So(err, ShouldEqual, bongo.RecordNotFound)

					testMessage := models.NewChannelMessage()
					err = bongo.B.Unscoped().Where("initial_channel_id = ?", channelContainer.Channel.Id).Find(testMessage).Error
					So(err, ShouldEqual, bongo.RecordNotFound)
				})
				Convey("Users should not be able to add/remove users to/from bot channels", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					participant := models.NewAccount()
					participant.OldId = AccountOldId.Hex()
					participant, err = rest.CreateAccount(participant)
					So(err, ShouldBeNil)
					So(participant, ShouldNotBeNil)

					ses, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_BOT, ses.ClientId)
					So(err, ShouldBeNil)
					So(ch, ShouldNotBeNil)

					// account is -> ownerAccount.Id
					_, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id)
					So(strings.Contains(err.Error(), "can not add participants for bot channel"), ShouldBeTrue)
				})

				Convey("Users should be able to add/remove users to/from topic channels", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					participant := models.NewAccount()
					participant.OldId = AccountOldId.Hex()
					participant, err = rest.CreateAccount(participant)
					So(err, ShouldBeNil)
					So(participant, ShouldNotBeNil)

					ses, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_TOPIC, ses.ClientId)
					So(err, ShouldBeNil)
					So(ch, ShouldNotBeNil)

					// account is -> ownerAccount.Id
					_, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id)
					So(err, ShouldBeNil)

					Convey("adding same user again should success", func() {
						_, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id)
						So(err, ShouldBeNil)
					})

					_, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant.Id)
					So(err, ShouldBeNil)

					Convey("removing same user again should success", func() {
						_, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant.Id)
						So(err, ShouldBeNil)
					})
				})
				Convey("User's notification setting should be removed when user left from channel", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					account := models.NewAccount()
					account.OldId = AccountOldId.Hex()
					account, err = rest.CreateAccount(account)
					So(err, ShouldBeNil)
					So(account, ShouldNotBeNil)

					_, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ses, err := models.FetchOrCreateSession(account.Nick, groupName)
					So(err, ShouldBeNil)
					So(ses, ShouldNotBeNil)

					channel := models.CreateTypedGroupedChannelWithTest(
						account.Id,
						models.Channel_TYPE_GROUP,
						groupName,
					)
					_, err = channel.AddParticipant(account.Id)
					So(err, ShouldBeNil)

					// create notification setting with fields
					n := &models.NotificationSetting{
						ChannelId:      channel.Id,
						AccountId:      account.Id,
						DesktopSetting: nisql.NullString{},
						MobileSetting:  nisql.String(models.NotificationSetting_STATUS_PERSONAL),
						IsMuted:        nisql.NullBool{},
						IsSuppressed:   nisql.Bool(true),
					}
					ns, err := rest.CreateNotificationSetting(n, ses.ClientId)
					So(err, ShouldBeNil)
					So(ns.AccountId, ShouldEqual, account.Id)
					So(*ns.IsSuppressed.Get(), ShouldEqual, true)
					So(ns.IsMuted.Get(), ShouldEqual, nil)
					So(ns.DesktopSetting.Get(), ShouldEqual, nil)

					Convey("Notification settings should be removed after participant removed", func() {
						// we can get notification settings before removing accoung from channel
						ns, err := rest.GetNotificationSetting(channel.Id, ses.ClientId)
						So(err, ShouldBeNil)
						So(ns, ShouldNotBeNil)

						err = channel.RemoveParticipant(account.Id)
						So(err, ShouldBeNil)

						// after removing account from channel, we should not be able to get notification setting
						ns, err = rest.GetNotificationSetting(channel.Id, ses.ClientId)
						So(err, ShouldNotBeNil)
						So(err.Error(), ShouldContainSubstring, models.ErrAccountIsNotParticipant.Error())
						So(ns, ShouldBeNil)
					})
				})
			})
		})
	})
}
<commit_msg>tests: add team leave/kick with non-owner account<commit_after>package main

import (
	"socialapi/models"
	"socialapi/rest"
	"socialapi/workers/common/tests"
	"strings"
	"testing"
	"time"

	"github.com/cihangir/nisql"
	"github.com/koding/bongo"
	"github.com/koding/runner"

	. "github.com/smartystreets/goconvey/convey"
)

func TestChannelParticipantOperations(t *testing.T) {
	tests.WithRunner(t, func(r *runner.Runner) {
		Convey("while testing channel participants", t, func() {
			Convey("First Create Users and initiate conversation", func() {
				ownerAccount, groupChannel, groupName := models.CreateRandomGroupDataWithChecks()

				ownerSes, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
				So(err, ShouldBeNil)
				So(ownerSes, ShouldNotBeNil)

				secondAccount, err := models.CreateAccountInBothDbs()
				tests.ResultedWithNoErrorCheck(secondAccount, err)
				_, err = groupChannel.AddParticipant(secondAccount.Id)
				So(err, ShouldBeNil)

				thirdAccount, err := models.CreateAccountInBothDbs()
				tests.ResultedWithNoErrorCheck(thirdAccount, err)
				_, err = groupChannel.AddParticipant(thirdAccount.Id)
				So(err, ShouldBeNil)

				forthAccount, err := models.CreateAccountInBothDbs()
				tests.ResultedWithNoErrorCheck(forthAccount, err)
				_, err = groupChannel.AddParticipant(forthAccount.Id)
				So(err, ShouldBeNil)

				devrim, err := models.CreateAccountInBothDbsWithNick("devrim")
				tests.ResultedWithNoErrorCheck(devrim, err)
				_, err = groupChannel.AddParticipant(devrim.Id)
				So(err, ShouldBeNil)

				ses, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
				tests.ResultedWithNoErrorCheck(ses, err)

				secondSes, err := models.FetchOrCreateSession(secondAccount.Nick, groupName)
				tests.ResultedWithNoErrorCheck(secondSes, err)

				pmr := models.ChannelRequest{}

				pmr.AccountId = ownerAccount.Id

				pmr.Body = "new conversation"
				pmr.GroupName = groupName
				pmr.Recipients = []string{"devrim"}

				channelContainer, err := rest.SendPrivateChannelRequest(pmr, ownerSes.ClientId)
				So(err, ShouldBeNil)
				So(channelContainer, ShouldNotBeNil)

				Convey("First user should be able to add second and third users to conversation", func() {
					_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id, thirdAccount.Id)
					So(err, ShouldBeNil)
					participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
					So(err, ShouldBeNil)
					So(participants, ShouldNotBeNil)
					// it is four because first user is "devrim" here
					So(len(participants), ShouldEqual, 4)

					Convey("First user should not be able to re-add second participant", func() {
						_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 4)
					})

					Convey("Second user should be able to leave conversation", func() {
						// token of account -> secondAccount
						_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 3)

						Convey("A user who is not participant of a conversation should not be able to add another user to the conversation", func() {
							_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, forthAccount.Id)
							So(err, ShouldNotBeNil)

							participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
							So(err, ShouldBeNil)
							So(participants, ShouldNotBeNil)
							So(len(participants), ShouldEqual, 3)
						})
					})

					Convey("Channel owner should be able to kick another conversation participant", func() {
						_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 3)
					})

					Convey("when a user is blocked", func() {
						_, err = rest.BlockChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
						So(err, ShouldBeNil)

						Convey("it should not be in channel participant list", func() {
							participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
							So(err, ShouldBeNil)
							So(participants, ShouldNotBeNil)
							So(len(participants), ShouldEqual, 3)
						})

						Convey("should not be able to add it back", func() {
							_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
							So(err, ShouldNotBeNil)
						})

						Convey("should be able to unblock", func() {
							_, err = rest.UnblockChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
							So(err, ShouldBeNil)

							Convey("it should not be in channel participant list still", func() {
								participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
								So(err, ShouldBeNil)
								So(participants, ShouldNotBeNil)
								So(len(participants), ShouldEqual, 3)
							})

							Convey("when we add the same user as participant", func() {
								_, err = rest.AddChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id, thirdAccount.Id)
								So(err, ShouldBeNil)

								Convey("it should be in channel participant list", func() {
									participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
									So(err, ShouldBeNil)
									So(participants, ShouldNotBeNil)
									So(len(participants), ShouldEqual, 4)
								})
							})
						})
					})

					Convey("Second user should not be able to kick another conversation participant", func() {
						_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, secondSes.ClientId, thirdAccount.Id)
						So(err, ShouldNotBeNil)
					})

				})
				Convey("First user should be able to invite second user", func() {
					_, err = rest.InviteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, secondAccount.Id)
					So(err, ShouldBeNil)
					participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
					So(err, ShouldBeNil)
					So(participants, ShouldNotBeNil)
					// it is four because first user is "devrim" here
					So(len(participants), ShouldEqual, 2)

					Convey("Second user should be able to reject invitation", func() {
						ses, err := models.FetchOrCreateSession(secondAccount.Nick, groupName)
						So(err, ShouldBeNil)
						So(ses, ShouldNotBeNil)

						err = rest.RejectInvitation(channelContainer.Channel.Id, ses.ClientId)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 2)
					})

					Convey("Second user should be able to accept invitation", func() {
						ses, err := models.FetchOrCreateSession(secondAccount.Nick, groupName)
						So(err, ShouldBeNil)
						So(ses, ShouldNotBeNil)

						err = rest.AcceptInvitation(channelContainer.Channel.Id, ses.ClientId)
						So(err, ShouldBeNil)

						participants, err := rest.ListChannelParticipants(channelContainer.Channel.Id, ownerSes.ClientId)
						So(err, ShouldBeNil)
						So(participants, ShouldNotBeNil)
						So(len(participants), ShouldEqual, 3)
					})
				})

				// TODO Until we find a better way for handling async stuff, this test is skipped. Instead of sleep, we should use some
				// timeouts for testing these kind of stuff.
				SkipConvey("All private messages must be deleted when all participant users leave the channel", func() {
					account := models.NewAccount()
					err = account.ByNick("devrim")
					So(err, ShouldBeNil)

					_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ses.ClientId, account.Id)
					So(err, ShouldBeNil)

					_, err = rest.DeleteChannelParticipant(channelContainer.Channel.Id, ownerSes.ClientId, ownerAccount.Id)
					So(err, ShouldBeNil)

					time.Sleep(1 * time.Second)

					testChannel := models.NewChannel()
					err := testChannel.ById(channelContainer.Channel.Id)
					So(err, ShouldEqual, bongo.RecordNotFound)

					testChannelList := models.NewChannelMessageList()
					err = bongo.B.Unscoped().Where("channel_id = ?", channelContainer.Channel.Id).Find(testChannelList).Error
					So(err, ShouldEqual, bongo.RecordNotFound)

					testMessage := models.NewChannelMessage()
					err = bongo.B.Unscoped().Where("initial_channel_id = ?", channelContainer.Channel.Id).Find(testMessage).Error
					So(err, ShouldEqual, bongo.RecordNotFound)
				})
				Convey("Users should not be able to add/remove users to/from bot channels", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					participant := models.NewAccount()
					participant.OldId = AccountOldId.Hex()
					participant, err = rest.CreateAccount(participant)
					So(err, ShouldBeNil)
					So(participant, ShouldNotBeNil)

					ses, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_BOT, ses.ClientId)
					So(err, ShouldBeNil)
					So(ch, ShouldNotBeNil)

					// account is -> ownerAccount.Id
					_, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id)
					So(strings.Contains(err.Error(), "can not add participants for bot channel"), ShouldBeTrue)
				})

				Convey("Users should be able to add/remove users to/from topic channels", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					participant := models.NewAccount()
					participant.OldId = AccountOldId.Hex()
					participant, err = rest.CreateAccount(participant)
					So(err, ShouldBeNil)
					So(participant, ShouldNotBeNil)

					ses, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_TOPIC, ses.ClientId)
					So(err, ShouldBeNil)
					So(ch, ShouldNotBeNil)

					// account is -> ownerAccount.Id
					_, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id)
					So(err, ShouldBeNil)

					Convey("adding same user again should success", func() {
						_, err = rest.AddChannelParticipant(ch.Id, ses.ClientId, participant.Id)
						So(err, ShouldBeNil)
					})

					_, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant.Id)
					So(err, ShouldBeNil)

					Convey("removing same user again should success", func() {
						_, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant.Id)
						So(err, ShouldBeNil)
					})
				})
				Convey("nonOwner should not be able to remove users from group channels", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					participant := models.NewAccount()
					participant.OldId = AccountOldId.Hex()
					participant, err = rest.CreateAccount(participant)
					So(err, ShouldBeNil)
					So(participant, ShouldNotBeNil)

					participant2 := models.NewAccount()
					participant2.OldId = AccountOldId.Hex()
					participant2, err = rest.CreateAccount(participant2)
					So(err, ShouldBeNil)
					So(participant2, ShouldNotBeNil)

					ownerSes, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ses, err := models.FetchOrCreateSession(participant.Nick, groupName)
					So(err, ShouldBeNil)

					ch, err := rest.CreateChannelByGroupNameAndType(ownerAccount.Id, groupName, models.Channel_TYPE_GROUP, ownerSes.ClientId)
					So(err, ShouldBeNil)
					So(ch, ShouldNotBeNil)

					// ownerSes session is admin's session data
					_, err = rest.AddChannelParticipant(ch.Id, ownerSes.ClientId, participant.Id)
					So(err, ShouldBeNil)

					_, err = rest.AddChannelParticipant(ch.Id, ownerSes.ClientId, participant2.Id)
					So(err, ShouldBeNil)

					// ses session is participant's session data
					_, err = rest.DeleteChannelParticipant(ch.Id, ses.ClientId, participant2.Id)
					So(err, ShouldNotBeNil)

				})
				Convey("User's notification setting should be removed when user left from channel", func() {
					ownerAccount, _, groupName := models.CreateRandomGroupDataWithChecks()

					account := models.NewAccount()
					account.OldId = AccountOldId.Hex()
					account, err = rest.CreateAccount(account)
					So(err, ShouldBeNil)
					So(account, ShouldNotBeNil)

					_, err := models.FetchOrCreateSession(ownerAccount.Nick, groupName)
					So(err, ShouldBeNil)

					ses, err := models.FetchOrCreateSession(account.Nick, groupName)
					So(err, ShouldBeNil)
					So(ses, ShouldNotBeNil)

					channel := models.CreateTypedGroupedChannelWithTest(
						account.Id,
						models.Channel_TYPE_GROUP,
						groupName,
					)
					_, err = channel.AddParticipant(account.Id)
					So(err, ShouldBeNil)

					// create notification setting with fields
					n := &models.NotificationSetting{
						ChannelId:      channel.Id,
						AccountId:      account.Id,
						DesktopSetting: nisql.NullString{},
						MobileSetting:  nisql.String(models.NotificationSetting_STATUS_PERSONAL),
						IsMuted:        nisql.NullBool{},
						IsSuppressed:   nisql.Bool(true),
					}
					ns, err := rest.CreateNotificationSetting(n, ses.ClientId)
					So(err, ShouldBeNil)
					So(ns.AccountId, ShouldEqual, account.Id)
					So(*ns.IsSuppressed.Get(), ShouldEqual, true)
					So(ns.IsMuted.Get(), ShouldEqual, nil)
					So(ns.DesktopSetting.Get(), ShouldEqual, nil)

					Convey("Notification settings should be removed after participant removed", func() {
						// we can get notification settings before removing accoung from channel
						ns, err := rest.GetNotificationSetting(channel.Id, ses.ClientId)
						So(err, ShouldBeNil)
						So(ns, ShouldNotBeNil)

						err = channel.RemoveParticipant(account.Id)
						So(err, ShouldBeNil)

						// after removing account from channel, we should not be able to get notification setting
						ns, err = rest.GetNotificationSetting(channel.Id, ses.ClientId)
						So(err, ShouldNotBeNil)
						So(err.Error(), ShouldContainSubstring, models.ErrAccountIsNotParticipant.Error())
						So(ns, ShouldBeNil)
					})
				})
			})
		})
	})
}
<|endoftext|>


<commit_before>package main

import (
	"koding/db/mongodb/modelhelper"
	"log"
	"socialapi/config"
	"socialapi/workers/collaboration"
	"socialapi/workers/collaboration/models"

	"github.com/koding/broker"
	"github.com/koding/runner"
)

var (
	Name = "Collaboration"
)

func main() {
	r := runner.New(Name)
	if err := r.Init(); err != nil {
		log.Fatal(err)
	}

	if r.Kite == nil {
		r.Log.Fatal("couldnt init kite")
	}

	// remove QOS, we want to consume all the messages from RMQ
	if err := r.Bongo.Broker.Sub.(*broker.Consumer).Consumer.QOS(0); err != nil {
		r.Log.Fatal("couldnt remove the QOS %# v", err)
	}

	redisConn := runner.MustInitRedisConn(r.Conf)
	defer redisConn.Close()

	appConfig := config.MustRead(r.Conf.Path)

	// init mongo connection
	modelhelper.Initialize(appConfig.Mongo)
	defer modelhelper.Close()

	handler := collaboration.New(r.Log, redisConn, appConfig, r.Kite)
	r.SetContext(handler)
	// only listen and operate on collaboration ping messages that are fired by the handler
	r.Register(models.Ping{}).On(collaboration.FireEventName).Handle((*collaboration.Controller).Ping)
	r.Listen()
	r.Wait()
}
<commit_msg>go/collaboration: remove redis & use mongo cache<commit_after>package main

import (
	"koding/db/mongodb/modelhelper"
	"log"
	"socialapi/config"
	"socialapi/workers/collaboration"
	"socialapi/workers/collaboration/models"

	"github.com/koding/broker"
	"github.com/koding/cache"
	"github.com/koding/runner"
)

var (
	// Name specifies runner name for collaboration
	Name = "Collaboration"
)

func main() {
	r := runner.New(Name)
	if err := r.Init(); err != nil {
		log.Fatal(err)
	}

	if r.Kite == nil {
		r.Log.Fatal("couldnt init kite")
	}

	// remove QOS, we want to consume all the messages from RMQ
	if err := r.Bongo.Broker.Sub.(*broker.Consumer).Consumer.QOS(0); err != nil {
		r.Log.Fatal("couldnt remove the QOS %# v", err)
	}

	appConfig := config.MustRead(r.Conf.Path)

	// init mongo connection
	modelhelper.Initialize(appConfig.Mongo)
	defer modelhelper.Close()

	// init with defaults & ensure expireAt index
	mongoCache := cache.NewMongoCacheWithTTL(modelhelper.Mongo.Session, cache.StartGC(), cache.MustEnsureIndexExpireAt())
	defer mongoCache.StopGC()

	handler := collaboration.New(r.Log, mongoCache, appConfig, r.Kite)
	r.SetContext(handler)
	// only listen and operate on collaboration ping messages that are fired by the handler
	r.Register(models.Ping{}).On(collaboration.FireEventName).Handle((*collaboration.Controller).Ping)
	r.Listen()
	r.Wait()
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Vitess 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 vreplication

import (
	"encoding/json"
	"fmt"

	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/vt/vtgate/evalengine"
)

const (
	createVReplicationLog = `CREATE TABLE IF NOT EXISTS _vt.vreplication_log (
		id BIGINT(20) AUTO_INCREMENT,
		vrepl_id INT NOT NULL,
		type VARBINARY(256) NOT NULL,
		state VARBINARY(100) NOT NULL,
		created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
		updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
		message text NOT NULL,
		count BIGINT(20) NOT NULL DEFAULT 1,
		PRIMARY KEY (id))`
)

const (
	// Enum values for type column of _vt.vreplication_log

	// LogStreamCreate is used when a row in _vt.vreplication is inserted via VReplicationExec
	LogStreamCreate = "Stream Created"
	// LogStreamUpdate is used when a row in _vt.vreplication is updated via VReplicationExec
	LogStreamUpdate = "Stream Updated"
	// LogStreamDelete is used when a row in _vt.vreplication is deleted via VReplicationExec
	LogStreamDelete = "Stream Deleted"
	// LogMessage is used for generic log messages
	LogMessage = "Message"
	// LogCopyStart is used when the copy phase is started
	LogCopyStart = "Started Copy Phase"
	// LogCopyEnd is used when the copy phase is done
	LogCopyEnd = "Ended Copy Phase"
	// LogStateChange is used when the state of the stream changes
	LogStateChange = "State Changed"

	// TODO: LogError is not used atm. Currently irrecoverable errors, resumable errors and informational messages
	//  are all treated the same: the message column is updated and state left as Running.
	//  Every five seconds we reset the message and retry streaming so that we can automatically resume from a temporary
	//  loss of connectivity or a reparent. We need to detect if errors are not recoverable and set an Error status.
	//  Since this device (of overloading the message) is strewn across the code, and incorrectly flagging resumable
	//  errors can stall workflows, we have deferred implementing it.

	// LogError indicates that there is an error from which we cannot recover and the operator needs to intervene.
	LogError = "Error"
)

func getLastLog(dbClient *vdbClient, vreplID uint32) (int64, string, string, string, error) {
	var qr *sqltypes.Result
	var err error
	query := fmt.Sprintf("select id, type, state, message from _vt.vreplication_log where vrepl_id = %d order by id desc limit 1", vreplID)
	if qr, err = dbClient.ExecuteFetch(query, 1); err != nil {
		return 0, "", "", "", err
	}
	if len(qr.Rows) != 1 || len(qr.Rows[0]) != 4 {
		return 0, "", "", "", nil
	}
	row := qr.Rows[0]
	id, _ := evalengine.ToInt64(row[0])
	typ := row[1].ToString()
	state := row[2].ToString()
	message := row[3].ToString()
	return id, typ, state, message, nil
}

func insertLog(dbClient *vdbClient, typ string, vreplID uint32, state, message string) error {
	var query string

	// getLastLog returns the last log for a stream. During insertion, if the id/type/state/message match we do not insert
	// a new log but increment the count. This prevents spamming of the log table in case the same message is logged continuously.
	id, currentType, currentState, currentMessage, err := getLastLog(dbClient, vreplID)
	if err != nil {
		return err
	}

	if id > 0 && typ == currentType && state == currentState && message == currentMessage {
		query = fmt.Sprintf("update _vt.vreplication_log set count = count + 1 where id = %d", id)
	} else {
		query = `insert into _vt.vreplication_log(vrepl_id, type, state, message) values(%d, '%s', '%s', %s)`
		query = fmt.Sprintf(query, vreplID, typ, state, encodeString(message))
	}

	if _, err := dbClient.ExecuteFetch(query, 1); err != nil {
		return fmt.Errorf("could not insert into log table: %v: %v", query, err)
	}
	return nil
}

// insertLogWithParams is called when a stream is created. The attributes of the stream are stored as a json string
func insertLogWithParams(dbClient *vdbClient, action string, vreplID uint32, params map[string]string) error {
	var message string
	if params != nil {
		obj, _ := json.Marshal(params)
		message = string(obj)
	}
	if err := insertLog(dbClient, action, vreplID, params["state"], message); err != nil {
		return err
	}
	return nil
}
<commit_msg>Use withDDL for vreplication log queries<commit_after>/*
Copyright 2021 The Vitess 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 vreplication

import (
	"context"
	"encoding/json"
	"fmt"

	"vitess.io/vitess/go/sqltypes"
	"vitess.io/vitess/go/vt/vtgate/evalengine"
)

const (
	createVReplicationLog = `CREATE TABLE IF NOT EXISTS _vt.vreplication_log (
		id BIGINT(20) AUTO_INCREMENT,
		vrepl_id INT NOT NULL,
		type VARBINARY(256) NOT NULL,
		state VARBINARY(100) NOT NULL,
		created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
		updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
		message text NOT NULL,
		count BIGINT(20) NOT NULL DEFAULT 1,
		PRIMARY KEY (id))`
)

const (
	// Enum values for type column of _vt.vreplication_log

	// LogStreamCreate is used when a row in _vt.vreplication is inserted via VReplicationExec
	LogStreamCreate = "Stream Created"
	// LogStreamUpdate is used when a row in _vt.vreplication is updated via VReplicationExec
	LogStreamUpdate = "Stream Updated"
	// LogStreamDelete is used when a row in _vt.vreplication is deleted via VReplicationExec
	LogStreamDelete = "Stream Deleted"
	// LogMessage is used for generic log messages
	LogMessage = "Message"
	// LogCopyStart is used when the copy phase is started
	LogCopyStart = "Started Copy Phase"
	// LogCopyEnd is used when the copy phase is done
	LogCopyEnd = "Ended Copy Phase"
	// LogStateChange is used when the state of the stream changes
	LogStateChange = "State Changed"

	// TODO: LogError is not used atm. Currently irrecoverable errors, resumable errors and informational messages
	//  are all treated the same: the message column is updated and state left as Running.
	//  Every five seconds we reset the message and retry streaming so that we can automatically resume from a temporary
	//  loss of connectivity or a reparent. We need to detect if errors are not recoverable and set an Error status.
	//  Since this device (of overloading the message) is strewn across the code, and incorrectly flagging resumable
	//  errors can stall workflows, we have deferred implementing it.

	// LogError indicates that there is an error from which we cannot recover and the operator needs to intervene.
	LogError = "Error"
)

func getLastLog(dbClient *vdbClient, vreplID uint32) (int64, string, string, string, error) {
	var qr *sqltypes.Result
	var err error
	query := fmt.Sprintf("select id, type, state, message from _vt.vreplication_log where vrepl_id = %d order by id desc limit 1", vreplID)
	if qr, err = withDDL.Exec(context.Background(), query, dbClient.ExecuteFetch); err != nil {
		return 0, "", "", "", err
	}
	if len(qr.Rows) != 1 || len(qr.Rows[0]) != 4 {
		return 0, "", "", "", nil
	}
	row := qr.Rows[0]
	id, _ := evalengine.ToInt64(row[0])
	typ := row[1].ToString()
	state := row[2].ToString()
	message := row[3].ToString()
	return id, typ, state, message, nil
}

func insertLog(dbClient *vdbClient, typ string, vreplID uint32, state, message string) error {
	var query string

	// getLastLog returns the last log for a stream. During insertion, if the id/type/state/message match we do not insert
	// a new log but increment the count. This prevents spamming of the log table in case the same message is logged continuously.
	id, currentType, currentState, currentMessage, err := getLastLog(dbClient, vreplID)
	if err != nil {
		return err
	}

	if id > 0 && typ == currentType && state == currentState && message == currentMessage {
		query = fmt.Sprintf("update _vt.vreplication_log set count = count + 1 where id = %d", id)
	} else {
		query = `insert into _vt.vreplication_log(vrepl_id, type, state, message) values(%d, '%s', '%s', %s)`
		query = fmt.Sprintf(query, vreplID, typ, state, encodeString(message))
	}

	if _, err = withDDL.Exec(context.Background(), query, dbClient.ExecuteFetch); err != nil {
		return fmt.Errorf("could not insert into log table: %v: %v", query, err)
	}
	return nil
}

// insertLogWithParams is called when a stream is created. The attributes of the stream are stored as a json string
func insertLogWithParams(dbClient *vdbClient, action string, vreplID uint32, params map[string]string) error {
	var message string
	if params != nil {
		obj, _ := json.Marshal(params)
		message = string(obj)
	}
	if err := insertLog(dbClient, action, vreplID, params["state"], message); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"

	"runtime"
	"strings"
	"syscall"

	"github.com/derekparker/delve/command"
	"github.com/derekparker/delve/goreadline"
	"github.com/derekparker/delve/proctl"
)

const version string = "0.1.3.beta"

type term struct {
	stdin *bufio.Reader
}

const historyFile string = ".dbg_history"

func main() {
	// We must ensure here that we are running on the same thread during
	// the execution of dbg. This is due to the fact that ptrace(2) expects
	// all commands after PTRACE_ATTACH to come from the same thread.
	runtime.LockOSThread()

	var (
		pid     int
		run     bool
		printv  bool
		err     error
		dbgproc *proctl.DebuggedProcess
		t       = newTerm()
		cmds    = command.DebugCommands()
	)

	flag.IntVar(&pid, "pid", 0, "Pid of running process to attach to.")
	flag.BoolVar(&run, "run", false, "Compile program and begin debug session.")
	flag.BoolVar(&printv, "v", false, "Print version number and exit.")
	flag.Parse()

	if flag.NFlag() == 0 && len(flag.Args()) == 0 {
		flag.Usage()
		os.Exit(0)
	}

	if printv {
		fmt.Printf("Delve version: %s\n", version)
		os.Exit(0)
	}

	switch {
	case run:
		const debugname = "debug"
		cmd := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
		err := cmd.Run()
		if err != nil {
			die(1, "Could not compile program:", err)
		}
		defer os.Remove(debugname)

		dbgproc, err = proctl.Launch(append([]string{"./" + debugname}, flag.Args()...))
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	case pid != 0:
		dbgproc, err = proctl.Attach(pid)
		if err != nil {
			die(1, "Could not attach to process:", err)
		}
	default:
		dbgproc, err = proctl.Launch(flag.Args())
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	}

	goreadline.LoadHistoryFromFile(historyFile)
	fmt.Println("Type 'help' for list of commands.")

	for {
		cmdstr, err := t.promptForInput()
		if err != nil {
			if err == io.EOF {
				handleExit(t, dbgproc, 0)
			}
			die(1, "Prompt for input failed.\n")
		}

		cmdstr, args := parseCommand(cmdstr)

		if cmdstr == "exit" {
			handleExit(t, dbgproc, 0)
		}

		cmd := cmds.Find(cmdstr)
		err = cmd(dbgproc, args...)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Command failed: %s\n", err)
		}
	}
}
func handleExit(t *term, dbp *proctl.DebuggedProcess, status int) {
	errno := goreadline.WriteHistoryToFile(historyFile)
	fmt.Println("readline:", errno)

	fmt.Println("Would you like to kill the process? [y/n]")
	answer, err := t.stdin.ReadString('\n')
	if err != nil {
		die(2, err.Error())
	}

	for pc := range dbp.BreakPoints {
		if _, err := dbp.Clear(pc); err != nil {
			fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err)
		}
	}

	fmt.Println("Detaching from process...")
	err = syscall.PtraceDetach(dbp.Process.Pid)
	if err != nil {
		die(2, "Could not detach", err)
	}

	if answer == "y\n" {
		fmt.Println("Killing process", dbp.Process.Pid)

		err := dbp.Process.Kill()
		if err != nil {
			fmt.Println("Could not kill process", err)
		}
	}

	die(status, "Hope I was of service hunting your bug!")
}

func die(status int, args ...interface{}) {
	fmt.Fprint(os.Stderr, args)
	fmt.Fprint(os.Stderr, "\n")
	os.Exit(status)
}

func newTerm() *term {
	return &term{
		stdin: bufio.NewReader(os.Stdin),
	}
}

func parseCommand(cmdstr string) (string, []string) {
	vals := strings.Split(cmdstr, " ")
	return vals[0], vals[1:]
}

func (t *term) promptForInput() (string, error) {
	prompt := "dlv> "
	linep := goreadline.ReadLine(&prompt)
	if linep == nil {
		return "", io.EOF
	}
	line := strings.TrimSuffix(*linep, "\n")
	if line != "" {
		goreadline.AddHistory(line)
	}

	return line, nil
}
<commit_msg>Move LockOSThread into init<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	"os/exec"

	"runtime"
	"strings"
	"syscall"

	"github.com/derekparker/delve/command"
	"github.com/derekparker/delve/goreadline"
	"github.com/derekparker/delve/proctl"
)

const version string = "0.1.3.beta"

type term struct {
	stdin *bufio.Reader
}

const historyFile string = ".dbg_history"

func init() {
	// We must ensure here that we are running on the same thread during
	// the execution of dbg. This is due to the fact that ptrace(2) expects
	// all commands after PTRACE_ATTACH to come from the same thread.
	runtime.LockOSThread()
}

func main() {
	var (
		pid     int
		run     bool
		printv  bool
		err     error
		dbgproc *proctl.DebuggedProcess
		t       = newTerm()
		cmds    = command.DebugCommands()
	)

	flag.IntVar(&pid, "pid", 0, "Pid of running process to attach to.")
	flag.BoolVar(&run, "run", false, "Compile program and begin debug session.")
	flag.BoolVar(&printv, "v", false, "Print version number and exit.")
	flag.Parse()

	if flag.NFlag() == 0 && len(flag.Args()) == 0 {
		flag.Usage()
		os.Exit(0)
	}

	if printv {
		fmt.Printf("Delve version: %s\n", version)
		os.Exit(0)
	}

	switch {
	case run:
		const debugname = "debug"
		cmd := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
		err := cmd.Run()
		if err != nil {
			die(1, "Could not compile program:", err)
		}
		defer os.Remove(debugname)

		dbgproc, err = proctl.Launch(append([]string{"./" + debugname}, flag.Args()...))
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	case pid != 0:
		dbgproc, err = proctl.Attach(pid)
		if err != nil {
			die(1, "Could not attach to process:", err)
		}
	default:
		dbgproc, err = proctl.Launch(flag.Args())
		if err != nil {
			die(1, "Could not launch program:", err)
		}
	}

	goreadline.LoadHistoryFromFile(historyFile)
	fmt.Println("Type 'help' for list of commands.")

	for {
		cmdstr, err := t.promptForInput()
		if err != nil {
			if err == io.EOF {
				handleExit(t, dbgproc, 0)
			}
			die(1, "Prompt for input failed.\n")
		}

		cmdstr, args := parseCommand(cmdstr)

		if cmdstr == "exit" {
			handleExit(t, dbgproc, 0)
		}

		cmd := cmds.Find(cmdstr)
		err = cmd(dbgproc, args...)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Command failed: %s\n", err)
		}
	}
}
func handleExit(t *term, dbp *proctl.DebuggedProcess, status int) {
	errno := goreadline.WriteHistoryToFile(historyFile)
	fmt.Println("readline:", errno)

	fmt.Println("Would you like to kill the process? [y/n]")
	answer, err := t.stdin.ReadString('\n')
	if err != nil {
		die(2, err.Error())
	}

	for pc := range dbp.BreakPoints {
		if _, err := dbp.Clear(pc); err != nil {
			fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err)
		}
	}

	fmt.Println("Detaching from process...")
	err = syscall.PtraceDetach(dbp.Process.Pid)
	if err != nil {
		die(2, "Could not detach", err)
	}

	if answer == "y\n" {
		fmt.Println("Killing process", dbp.Process.Pid)

		err := dbp.Process.Kill()
		if err != nil {
			fmt.Println("Could not kill process", err)
		}
	}

	die(status, "Hope I was of service hunting your bug!")
}

func die(status int, args ...interface{}) {
	fmt.Fprint(os.Stderr, args)
	fmt.Fprint(os.Stderr, "\n")
	os.Exit(status)
}

func newTerm() *term {
	return &term{
		stdin: bufio.NewReader(os.Stdin),
	}
}

func parseCommand(cmdstr string) (string, []string) {
	vals := strings.Split(cmdstr, " ")
	return vals[0], vals[1:]
}

func (t *term) promptForInput() (string, error) {
	prompt := "dlv> "
	linep := goreadline.ReadLine(&prompt)
	if linep == nil {
		return "", io.EOF
	}
	line := strings.TrimSuffix(*linep, "\n")
	if line != "" {
		goreadline.AddHistory(line)
	}

	return line, nil
}
<|endoftext|>


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

package cmd

import (
	"errors"
	"fmt"
	"go/build"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/mholt/archiver"
	"github.com/urfave/cli"
)

var (
	ErrNotSupported = errors.New("The package path is not supported")
)

// downloadFromGithub download from github
func downloadFromGithub(pkg, refName, dstDir string) error {
	//https://github.com/go-gitea/gitea/archive/master.zip
	if !strings.HasPrefix(pkg, "github.com") {
		return ErrNotSupported
	}

	pkgCachePath := filepath.Join(dstDir, refName+".zip")
	if IsExist(pkgCachePath) {
		return nil
	}

	url := fmt.Sprintf("https://%s/archive/%s.zip", pkg, refName)
	Println("Downloading from", url)
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	f, err := ioutil.TempFile(os.TempDir(), "github.com")
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, resp.Body)
	if err != nil {
		return err
	}

	if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
		return err
	}

	return os.Rename(f.Name(), pkgCachePath)
}

// downloadFromGopm download from gopm.io
func downloadFromGopm(pkg, refName, dstDir string) error {
	pkgCachePath := filepath.Join(dstDir, refName+".zip")
	if IsExist(pkgCachePath) {
		return nil
	}

	url := fmt.Sprintf("https://gopm.io/api/v1/download?pkgname=%s&revision=%s", pkg, refName)
	Println("Downloading from", url)
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	f, err := ioutil.TempFile(os.TempDir(), "gopm.io")
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, resp.Body)
	if err != nil {
		return err
	}

	if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
		return err
	}

	return os.Rename(f.Name(), pkgCachePath)
}

// CmdDownload represents download a package from github or gopm.io
var CmdDownload = cli.Command{
	Name:        "dl",
	Usage:       "Download one or more packages",
	Description: `Download one or more packages`,
	Action:      runDownload,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "Enables verbose progress and debug output",
		},
		cli.BoolFlag{
			Name:  "recursive, r",
			Usage: "Also download all the dependent packages",
		},
		cli.StringFlag{
			Name:  "source, s",
			Usage: "Download source",
		},
		cli.StringFlag{
			Name:  "target, t",
			Usage: "Download target directory",
		},
	},
}

func runDownload(ctx *cli.Context) error {
	if len(ctx.Args()) <= 0 {
		return errors.New("You have to indicate one or more packages")
	}

	homeDir, err := Home()
	if err != nil {
		return err
	}

	err = loadGlobalConfig(filepath.Join(homeDir, ".gop.yml"))
	if err != nil {
		return err
	}

	showLog = ctx.IsSet("verbose")
	names := ctx.Args()
	for _, name := range names {
		if err := download(ctx, name); err != nil {
			fmt.Println(err)
		}
	}
	return nil
}

func download(ctx *cli.Context, pkg string) error {
	pkgPaths := strings.Split(pkg, "/")
	var rootDir = globalConfig.Get("repos.default_dir")
	if ctx.String("target") != "" {
		rootDir = ctx.String("target")
	}
	dstDir := filepath.Join(append([]string{rootDir}, pkgPaths...)...)

	var err error
	switch ctx.String("source") {
	case "origin":
		err = downloadFromGithub(pkg, "master", dstDir)
	case "gopm":
		err = downloadFromGopm(pkg, "master", dstDir)
	default:
		err := downloadFromGithub(pkg, "master", dstDir)
		if err != nil {
			Println("Downloading failed:", err)
			err = downloadFromGopm(pkg, "master", dstDir)
		}
	}
	if err != nil {
		return err
	}

	if !ctx.Bool("recursive") {
		return nil
	}

	// extract files to a tempory directory
	tmpDir, err := ioutil.TempDir(os.TempDir(), strings.Replace(pkg, "/", "_", -1))
	if err != nil {
		return err
	}

	err = archiver.Zip.Open(filepath.Join(dstDir, "master.zip"), tmpDir)
	if err != nil {
		return err
	}

	ctxt := build.Default
	globalGoPath, ok := os.LookupEnv("GOPATH")
	if !ok {
		return errors.New("Not found GOPATH")
	}

	ctxt.GOPATH = globalGoPath
	if Debug {
		log.Printf("Import/root path: %s\n", tmpDir)
		log.Printf("Context GOPATH: %s\n", ctxt.GOPATH)
		log.Printf("Srouce path: %s\n", tmpDir)
	}
	dependentPkgs, err := ctxt.Import(pkg, tmpDir, build.AllowBinary)
	if err != nil {
		if _, ok := err.(*build.NoGoError); !ok {
			return fmt.Errorf("fail to get imports(%s): %v", pkg, err)
		}
		log.Printf("Getting imports: %v\n", err)
	}

	for _, subPkg := range dependentPkgs.Imports {
		if IsGoRepoPath(subPkg) ||
			strings.HasPrefix(subPkg, pkg) {
			continue
		}

		if err = download(ctx, subPkg); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>add override arg for download (#21)<commit_after>// Copyright 2017 The Gop Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package cmd

import (
	"errors"
	"fmt"
	"go/build"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/mholt/archiver"
	"github.com/urfave/cli"
)

var (
	ErrNotSupported = errors.New("The package path is not supported")
)

// downloadFromGithub download from github
func downloadFromGithub(ctx *cli.Context, pkg, refName, dstDir string) error {
	//https://github.com/go-gitea/gitea/archive/master.zip
	if !strings.HasPrefix(pkg, "github.com") {
		return ErrNotSupported
	}

	pkgCachePath := filepath.Join(dstDir, refName+".zip")
	if !ctx.Bool("override") && IsExist(pkgCachePath) {
		return nil
	}

	url := fmt.Sprintf("https://%s/archive/%s.zip", pkg, refName)
	Println("Downloading from", url)
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	f, err := ioutil.TempFile(os.TempDir(), "github.com")
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, resp.Body)
	if err != nil {
		return err
	}

	if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
		return err
	}

	return os.Rename(f.Name(), pkgCachePath)
}

// downloadFromGopm download from gopm.io
func downloadFromGopm(ctx *cli.Context, pkg, refName, dstDir string) error {
	pkgCachePath := filepath.Join(dstDir, refName+".zip")
	if !ctx.Bool("override") && IsExist(pkgCachePath) {
		return nil
	}

	url := fmt.Sprintf("https://gopm.io/api/v1/download?pkgname=%s&revision=%s", pkg, refName)
	Println("Downloading from", url)
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	f, err := ioutil.TempFile(os.TempDir(), "gopm.io")
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, resp.Body)
	if err != nil {
		return err
	}

	if err = os.MkdirAll(dstDir, os.ModePerm); err != nil {
		return err
	}

	return os.Rename(f.Name(), pkgCachePath)
}

// CmdDownload represents download a package from github or gopm.io
var CmdDownload = cli.Command{
	Name:        "dl",
	Usage:       "Download one or more packages",
	Description: `Download one or more packages`,
	Action:      runDownload,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "override, o",
			Usage: "Download packages even it exists.",
		},
		cli.BoolFlag{
			Name:  "recursive, r",
			Usage: "Also download all the dependent packages",
		},
		cli.StringFlag{
			Name:  "source, s",
			Usage: "Download source",
		},
		cli.StringFlag{
			Name:  "target, t",
			Usage: "Download target directory",
		},
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "Enables verbose progress and debug output",
		},
	},
}

func runDownload(ctx *cli.Context) error {
	if len(ctx.Args()) <= 0 {
		return errors.New("You have to indicate one or more packages")
	}

	homeDir, err := Home()
	if err != nil {
		return err
	}

	err = loadGlobalConfig(filepath.Join(homeDir, ".gop.yml"))
	if err != nil {
		return err
	}

	showLog = ctx.IsSet("verbose")
	names := ctx.Args()
	for _, name := range names {
		if err := download(ctx, name); err != nil {
			fmt.Println(err)
		}
	}
	return nil
}

func download(ctx *cli.Context, pkg string) error {
	pkgPaths := strings.Split(pkg, "/")
	var rootDir = globalConfig.Get("repos.default_dir")
	if ctx.String("target") != "" {
		rootDir = ctx.String("target")
	}
	dstDir := filepath.Join(append([]string{rootDir}, pkgPaths...)...)

	var err error
	switch ctx.String("source") {
	case "origin":
		err = downloadFromGithub(ctx, pkg, "master", dstDir)
	case "gopm":
		err = downloadFromGopm(ctx, pkg, "master", dstDir)
	default:
		err := downloadFromGithub(ctx, pkg, "master", dstDir)
		if err != nil {
			Println("Downloading failed:", err)
			err = downloadFromGopm(ctx, pkg, "master", dstDir)
		}
	}
	if err != nil {
		return err
	}

	if !ctx.Bool("recursive") {
		return nil
	}

	// extract files to a tempory directory
	tmpDir, err := ioutil.TempDir(os.TempDir(), strings.Replace(pkg, "/", "_", -1))
	if err != nil {
		return err
	}

	err = archiver.Zip.Open(filepath.Join(dstDir, "master.zip"), tmpDir)
	if err != nil {
		return err
	}

	ctxt := build.Default
	globalGoPath, ok := os.LookupEnv("GOPATH")
	if !ok {
		return errors.New("Not found GOPATH")
	}

	ctxt.GOPATH = globalGoPath
	if Debug {
		log.Printf("Import/root path: %s\n", tmpDir)
		log.Printf("Context GOPATH: %s\n", ctxt.GOPATH)
		log.Printf("Srouce path: %s\n", tmpDir)
	}
	dependentPkgs, err := ctxt.Import(pkg, tmpDir, build.AllowBinary)
	if err != nil {
		if _, ok := err.(*build.NoGoError); !ok {
			return fmt.Errorf("fail to get imports(%s): %v", pkg, err)
		}
		log.Printf("Getting imports: %v\n", err)
	}

	for _, subPkg := range dependentPkgs.Imports {
		if IsGoRepoPath(subPkg) ||
			strings.HasPrefix(subPkg, pkg) {
			continue
		}

		if err = download(ctx, subPkg); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>