Skip to content

Commit

Permalink
Merge pull request #92 from adlio/Rukenshia-feature/card-start-date
Browse files Browse the repository at this point in the history
Rukenshia/trello Merge
  • Loading branch information
adlio committed Mar 28, 2022
2 parents 1025e08 + 08501d6 commit 0bbe792
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 30 deletions.
2 changes: 0 additions & 2 deletions TODO.txt

This file was deleted.

7 changes: 7 additions & 0 deletions card.go
Expand Up @@ -43,6 +43,7 @@ type Card struct {
ShortURL string `json:"shortUrl"`
URL string `json:"url"`
Desc string `json:"desc"`
Start *time.Time `json:"start"`
Due *time.Time `json:"due"`
DueComplete bool `json:"dueComplete"`
Closed bool `json:"closed"`
Expand Down Expand Up @@ -275,6 +276,9 @@ func (c *Client) CreateCard(card *Card, extraArgs ...Arguments) error {
if card.Due != nil {
args["due"] = card.Due.Format(time.RFC3339)
}
if card.Start != nil {
args["start"] = card.Start.Format(time.RFC3339)
}

args.flatten(extraArgs)
err := c.Post(path, args, &card)
Expand All @@ -296,6 +300,9 @@ func (l *List) AddCard(card *Card, extraArgs ...Arguments) error {
if card.Due != nil {
args["due"] = card.Due.Format(time.RFC3339)
}
if card.Start != nil {
args["start"] = card.Start.Format(time.RFC3339)
}

args.flatten(extraArgs)

Expand Down
83 changes: 55 additions & 28 deletions card_test.go
Expand Up @@ -6,6 +6,7 @@
package trello

import (
"net/http"
"testing"
"time"
)
Expand All @@ -25,10 +26,10 @@ func TestCardCreatedAt(t *testing.T) {
func TestGetCardsOnBoard(t *testing.T) {
board := testBoard(t)

server := mockDynamicPathResponse()
server := NewMockResponder(t)
defer server.Close()

board.client.BaseURL = server.URL
board.client.BaseURL = server.URL()
cards, err := board.GetCards(Defaults())
if err != nil {
t.Fatal(err)
Expand All @@ -41,9 +42,9 @@ func TestGetCardsOnBoard(t *testing.T) {
func TestGetCardsInList(t *testing.T) {
list := testList(t)

server := mockResponse("cards", "list-cards-api-example.json")
server := NewMockResponder(t, "cards", "list-cards-api-example.json")
defer server.Close()
list.client.BaseURL = server.URL
list.client.BaseURL = server.URL()

cards, err := list.GetCards(Defaults())
if err != nil {
Expand All @@ -57,9 +58,9 @@ func TestGetCardsInList(t *testing.T) {
func TestCardsCustomFields(t *testing.T) {
list := testList(t)

server := mockResponse("cards", "list-cards-api-example.json")
server := NewMockResponder(t, "cards", "list-cards-api-example.json")
defer server.Close()
list.client.BaseURL = server.URL
list.client.BaseURL = server.URL()

cards, err := list.GetCards(Defaults())
if err != nil {
Expand Down Expand Up @@ -94,10 +95,10 @@ func TestCardsCustomFields(t *testing.T) {
func TestBoardContainsCopyOfCard(t *testing.T) {
board := testBoard(t)

server := mockResponse("actions", "board-actions-copyCard.json")
server := NewMockResponder(t, "actions", "board-actions-copyCard.json")
defer server.Close()

board.client.BaseURL = server.URL
board.client.BaseURL = server.URL()
firstResult, err := board.ContainsCopyOfCard("57f50c552b96e3fffe588aad", Defaults())
if err != nil {
t.Error(err)
Expand All @@ -117,16 +118,29 @@ func TestBoardContainsCopyOfCard(t *testing.T) {

func TestCreateCard(t *testing.T) {
c := testClient()
server := mockResponse("cards", "card-create.json")
server := NewMockResponder(t, "cards", "card-create.json")
defer server.Close()

c.BaseURL = server.URL
server.AssertRequest(func(t *testing.T, r *http.Request) {
due := r.URL.Query().Get("due")
if _, err := time.Parse(time.RFC3339, due); err != nil {
t.Errorf("Expected due to be in RFC3339 format, but value was '%v'", due)
}

start := r.URL.Query().Get("start")
if _, err := time.Parse(time.RFC3339, start); err != nil {
t.Errorf("Expected start to be in RFC3339 format, but value was '%v'", start)
}
})

c.BaseURL = server.URL()
dueDate := time.Now().AddDate(0, 0, 3)
startDate := time.Now().AddDate(0, 0, 2)

card := Card{
Name: "Test Card Create",
Desc: "What its about",
Due: &dueDate,
Start: &startDate,
IDList: "57f03a06b5ff33a63c8be316",
IDLabels: []string{"label1", "label2"},
}
Expand Down Expand Up @@ -156,15 +170,28 @@ func TestCreateCard(t *testing.T) {
func TestAddCardToList(t *testing.T) {
l := testList(t)

server := mockResponse("cards", "card-posted-to-bottom-of-list.json")
server := NewMockResponder(t, "cards", "card-posted-to-bottom-of-list.json")
server.AssertRequest(func(t *testing.T, r *http.Request) {
due := r.URL.Query().Get("due")
if _, err := time.Parse(time.RFC3339, due); err != nil {
t.Errorf("Expected due to be in RFC3339 format, but value was '%v'", due)
}

start := r.URL.Query().Get("start")
if _, err := time.Parse(time.RFC3339, start); err != nil {
t.Errorf("Expected start to be in RFC3339 format, but value was '%v'", start)
}
})
defer server.Close()
l.client.BaseURL = server.URL
dueDate := time.Now().AddDate(0, 0, 1)
l.client.BaseURL = server.URL()
dueDate := time.Now().AddDate(0, 0, 2)
startDate := time.Now().AddDate(0, 0, 1)

card := Card{
Name: "Test Card POSTed to List",
Desc: "This is its description.",
Due: &dueDate,
Start: &startDate,
IDLabels: []string{"label1", "label2"},
}

Expand Down Expand Up @@ -193,16 +220,16 @@ func TestAddCardToList(t *testing.T) {
func TestArchiveUnarchive(t *testing.T) {
c := testCard(t)

server := mockResponse("cards", "card-archived.json")
c.client.BaseURL = server.URL
server := NewMockResponder(t, "cards", "card-archived.json")
c.client.BaseURL = server.URL()
c.Archive()
if c.Closed == false {
t.Errorf("Card should have been archived.")
}
server.Close()

server = mockResponse("cards", "card-unarchived.json")
c.client.BaseURL = server.URL
server = NewMockResponder(t, "cards", "card-unarchived.json")
c.client.BaseURL = server.URL()
c.Unarchive()
if c.Closed == true {
t.Errorf("Card should have been unarchived.")
Expand All @@ -213,9 +240,9 @@ func TestArchiveUnarchive(t *testing.T) {
func TestCopyCardToList(t *testing.T) {
c := testCard(t)

server := mockResponse("cards", "card-copied.json")
server := NewMockResponder(t, "cards", "card-copied.json")
defer server.Close()
c.client.BaseURL = server.URL
c.client.BaseURL = server.URL()

newCard, err := c.CopyToList("57f03a022cd45c863ca581f1", Defaults())
if err != nil {
Expand All @@ -234,9 +261,9 @@ func TestCopyCardToList(t *testing.T) {
func TestGetParentCard(t *testing.T) {
c := testCard(t)

server := mockDynamicPathResponse()
server := NewMockResponder(t)
defer server.Close()
c.client.BaseURL = server.URL
c.client.BaseURL = server.URL()

parent, err := c.GetParentCard(Defaults())
if err != nil {
Expand Down Expand Up @@ -265,10 +292,10 @@ func TestGetAncestorCards(t *testing.T) {

func TestAddMemberIdToCard(t *testing.T) {
c := testCard(t)
server := mockResponse("cards", "card-add-member-response.json")
server := NewMockResponder(t, "cards", "card-add-member-response.json")
defer server.Close()

c.client.BaseURL = server.URL
c.client.BaseURL = server.URL()
member, err := c.AddMemberID("testmemberid")
if err != nil {
t.Error(err)
Expand All @@ -283,10 +310,10 @@ func TestAddMemberIdToCard(t *testing.T) {

func TestAddURLAttachmentToCard(t *testing.T) {
c := testCard(t)
server := mockResponse("cards", "url-attachments.json")
server := NewMockResponder(t, "cards", "url-attachments.json")
defer server.Close()

c.client.BaseURL = server.URL
c.client.BaseURL = server.URL()
attachment := Attachment{
Name: "Test",
URL: "https://github.com/test",
Expand All @@ -313,10 +340,10 @@ func TestCardSetClient(t *testing.T) {
//
func testCard(t *testing.T) *Card {
c := testClient()
server := mockResponse("cards", "card-api-example.json")
server := NewMockResponder(t, "cards", "card-api-example.json")
defer server.Close()

c.BaseURL = server.URL
c.BaseURL = server.URL()
card, err := c.GetCard("4eea503", Defaults())
if err != nil {
t.Fatal(err)
Expand Down
153 changes: 153 additions & 0 deletions mock-responder_test.go
@@ -0,0 +1,153 @@
package trello

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

// MockResponder is a thin wrapper around the httptest.Server. It adds the
// ability to specify a file or directory of files to use as mock responses,
// and provides the AsertRequest method to add test assertions that requests
// are being made correctly. Just like with httptest.Server, the caller should
// defer a call to .Close() to shutdown the server when all requests complete.
// MockResponders should be created via the NewMockResponder() constructor.
//
type MockResponder interface {
Close()
URL() string
AssertRequest(func(t *testing.T, r *http.Request))
}

type mockResponder struct {
t *testing.T

// server will be nil until .URL() is called the first time
server *httptest.Server

// requestAssertions is a list of functions which is called on
// each HTTP request before finding and returning the mock response content.
// They should be used to make assertions on the contents of the HTTP
// request being made against this MockResponder
requestAssertions []func(t *testing.T, r *http.Request)

// mockPath holds the results of filepath.Join on the provided path parts.
// The constructor verifies the existence of the path, so this will always
// hold a valid path to either a mock file, or a directory of many mocks
mockPath string

// useDynamicPaths is set to true when mockPath is a directory. It triggers
// code which determies the mock file from the path of incoming HTTP
// requests
useDynamicPaths bool
}

// NewMockResponder creates a new MockResponder instance around the provided
// test case. The mockPath is the relative filesystem path under ./testdata/
// where the mock response JSON can be found.
//
// If mockPath describes the path to a *file*, then that file
// will be used for ALL requests. If the path is a directory, then the mock
// response will be built dynamically from the path of the request (e.g.
// GET /subdir/folder/file will return the file at subdir/folder/file.json,
// assuming it exists). This latter mode is described as "dynamic paths". When
// requests arrive with querystring arguments, the dynamic path builder will
// compute an MD5 hash of the arguments and include that as a suffix of the
// mock file path.
//
// If no mockPath is provided, then the MockResponder will run in dynamic path
// mode from the root of the testdata/ directory.
//
// The caller is expected to defer a call .Close() after NewMockResponder().
//
func NewMockResponder(t *testing.T, mockPath ...string) MockResponder {
r := &mockResponder{t: t}

// Verify a valid path was provided
r.mockPath = filepath.Join(append([]string{".", "testdata"}, mockPath...)...)
fi, err := os.Stat(r.mockPath)
if err != nil {
log.Fatalf("invalid mock path %v: %s", mockPath, err)
}

// If the provided mockPath points to a directory, then
// we'll figure out the ultimate path dynamically as requests occur.
r.useDynamicPaths = fi.IsDir()

return r
}

// AssertRequest adds a new function to be run on each HTTP request the mock
// responder recveives. Its intended use is to make test assertions on the
// content of the request
func (mr *mockResponder) AssertRequest(ra func(t *testing.T, r *http.Request)) {
mr.requestAssertions = append(mr.requestAssertions, ra)
}

// Close wraps the *httptest.Server's Close method
func (mr *mockResponder) Close() {
if mr.server != nil {
mr.server.Close()
}
}

// URL is equivalent to the *httptest.Server property of the same name, but it
// is responsible for *creating* the *httptest.Server. This function should
// be called after all customization (including calls to AssertRequest) is
// complete.
//
func (mr *mockResponder) URL() string {
if mr.server != nil {
mr.t.Error("URL() should only be called once, after completing configuration")
}
mr.server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
for _, assertion := range mr.requestAssertions {
assertion(mr.t, r)
}
mr.mockHandler(rw, r)
}))
return mr.server.URL
}

// mockHandler is the http.HandlerFunc for the httptest.Server inside the
// mockResponder. When the mockPath points to a single file, it simply returns
// that file in the HTTP response. Otherwise it dynamically determines the
// path of the mock file to use and returns that if the file is found...
// otherwise it responds with an error instructing the user where to put their
// mock file.
//
func (mr *mockResponder) mockHandler(rw http.ResponseWriter, r *http.Request) {
var filename string
if mr.useDynamicPaths {
parts := []string{mr.mockPath}
parts = append(parts, strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")...)
queryStringPart := strings.Replace(r.URL.RawQuery, "key=user&token=pass", "", -1)
if queryStringPart != "" {
parts[len(parts)-1] = fmt.Sprintf("%s-%x", parts[len(parts)-1], md5.Sum([]byte(queryStringPart)))
}

filename = filepath.Join(parts...)
if !strings.HasSuffix(filename, ".json") {
filename = filename + ".json"
}
if _, err := os.Stat(filename); err != nil {
http.Error(rw, fmt.Sprintf("%s doesn't exist or couldn't be read. Create it with the mock you'd like to use.\n Args were: %s", filename, queryStringPart), http.StatusNotFound)
return
}
} else {
filename = mr.mockPath
}

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

0 comments on commit 0bbe792

Please sign in to comment.