Merge lp:~prudhvikrishna/goamz/sns into lp:~niemeyer/goamz/aws

Proposed by Prudhvi Surapaneni
Status: Needs review
Proposed branch: lp:~prudhvikrishna/goamz/sns
Merge into: lp:~niemeyer/goamz/aws
Diff against target: 1084 lines (+1047/-0) (has conflicts)
7 files modified
Makefile (+21/-0)
README (+1/-0)
responses_test.go (+164/-0)
sign.go (+64/-0)
sns.go (+428/-0)
sns_test.go (+230/-0)
suite_test.go (+139/-0)
Conflict adding file Makefile.  Moved existing file to Makefile.moved.
Conflict adding file suite_test.go.  Moved existing file to suite_test.go.moved.
To merge this branch: bzr merge lp:~prudhvikrishna/goamz/sns
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Needs Fixing
Review via email: mp+76465@code.launchpad.net

Description of the change

New SNS Related Code.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Download full text (5.3 KiB)

This looks fantastic overall.

I have several points that are mainly related to conventions and idiomatic
coding for Go, but nothing really major or hard.

Please push it again and ping me once you have these ready, and let me
know if you have any questions on these.

[1]

> type Message struct {
(...)
> Message [8192]byte
(...)
> func (topic *Topic) Message(message [8192]byte, subject string) *Message {
> return &Message{topic.SNS, topic, message, subject}
> }

Both of these should use []byte rather than [8192]byte, and the field should
be named something like Payload instead of Message (Message.Message isn't
nice).

That said, why do we need the Message struct at all? It doesn't look
like it's used anywhere, so we can just drop it.

[2]

> type PublishOpt struct {
> (...)
> func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResponse, err os.Error) {

This is one of the most interesting methods in the interface, and the function signature
feels a bit unfortunate to be using frequently. Let's organize it like this:

func (sns *SNS) Publish(message []byte, topic string)
func (sns *SNS) PublishSubject(message []byte, topic string, subject string)
func (sns *SNS) PublishStructure(message []byte, topic string, subject string, structure string)

The first two are actually backed by the last one.

[3]

> type Topic struct {
> *SNS
> TopicArn string
> }

s/TopicArn/ARN/

I've made the mistake of duplicating the type name in other places, but I want
to get it fixed at some point. The structure name is already Topic, so there's
no need to put Topic in the field name as well.

Also, let's please do s/Arn/ARN/ in the rest of the code in the Go side. This
is an acronym, so should be capitalized (like SNS itself).

[4]

> type Subscription struct {
> Endpoint string
> Owner string
> Protocol string
> SubscriptionArn string
> TopicArn string
> }

Similarly, s/SubscriptionArn/ARN/, and s/TopicArn/TopicARN/.

[5]

> type AttributeEntry struct {
> Key, Value string
> }
>
> type GetTopicAttributesResponse struct {
> Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"`
> ResponseMetadata
> }

Let's please try to avoid such long names:

s/Attributes/Attrs/
s/Response/Resp/

across the whole code, besides the necessary strings to make
Amazon happy, and ResponseMetadata which feels like a convenient
way to parse that recurring blob of data.

[6]

> type GetTopicAttributesResponse struct {
> Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"`

Then, the Attrs field in that type is actually a map, and users will be
much happier to handle it as such in the Go side. Define an internal struct
so that you can parse the XML out, and then internally translate it into
the following:

type GetTopicAttrsResp struct {
    Attrs map[string]string
    ResponseMetadata
}

[7]

> type SetTopicAttributesResponse struct {
> ResponseMetadata
> }
>
> type DeleteTopicResponse struct {
> ResponseMetadata
> }
> type RemovePermissionResponse struct {
> ResponseMetadata
> }
(...)

There are ...

Read more...

review: Needs Fixing

Unmerged revisions

24. By Prudhvi Surapaneni

Refactor to be consistent

23. By Prudhvi Surapaneni

Add Comments

22. By Prudhvi Surapaneni

Add Functionality for ListSubscriptionsByTopic

21. By Prudhvi Surapaneni

Add functionality for RemovePermission

20. By Prudhvi Surapaneni

Add functionality for AddPermission

19. By Prudhvi Surapaneni

Add functionality for ConfirmSubscription

18. By Prudhvi Surapaneni

Add functionality for Unsubscribe

17. By Prudhvi Surapaneni

gofmt changes

16. By Prudhvi Surapaneni

Add functionality for Subscribe

15. By Prudhvi Surapaneni

Add functionality for SetTopicAttributes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'Makefile'
2--- Makefile 1970-01-01 00:00:00 +0000
3+++ Makefile 2011-09-21 19:16:26 +0000
4@@ -0,0 +1,21 @@
5+include $(GOROOT)/src/Make.inc
6+
7+TARG=launchpad.net/goamz/sns
8+
9+GOFILES=\
10+ sns.go\
11+ sign.go\
12+
13+include $(GOROOT)/src/Make.pkg
14+
15+GOFMT=gofmt
16+BADFMT=$(shell $(GOFMT) -l $(GOFILES) 2> /dev/null)
17+
18+gofmt: $(BADFMT)
19+ @for F in $(BADFMT); do $(GOFMT) -w $$F && echo $$F; done
20+
21+ifneq ($(BADFMT),)
22+ifneq ($(MAKECMDGOALS), gofmt)
23+#$(warning WARNING: make gofmt: $(BADFMT))
24+endif
25+endif
26
27=== renamed file 'Makefile' => 'Makefile.moved'
28=== added file 'README'
29--- README 1970-01-01 00:00:00 +0000
30+++ README 2011-09-21 19:16:26 +0000
31@@ -0,0 +1,1 @@
32+Amazon Simple Notification Service API for Golang.
33
34=== added file 'responses_test.go'
35--- responses_test.go 1970-01-01 00:00:00 +0000
36+++ responses_test.go 2011-09-21 19:16:26 +0000
37@@ -0,0 +1,164 @@
38+package sns_test
39+
40+var TestListTopicsXmlOK = `
41+<?xml version="1.0"?>
42+<ListTopicsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
43+ <ListTopicsResult>
44+ <Topics>
45+ <member>
46+ <TopicArn>arn:aws:sns:us-west-1:331995417492:Transcoding</TopicArn>
47+ </member>
48+ </Topics>
49+ </ListTopicsResult>
50+ <ResponseMetadata>
51+ <RequestId>bd10b26c-e30e-11e0-ba29-93c3aca2f103</RequestId>
52+ </ResponseMetadata>
53+</ListTopicsResponse>
54+`
55+
56+var TestCreateTopicXmlOK = `
57+<?xml version="1.0"?>
58+<CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
59+ <CreateTopicResult>
60+ <TopicArn>arn:aws:sns:us-east-1:123456789012:My-Topic</TopicArn>
61+ </CreateTopicResult>
62+ <ResponseMetadata>
63+ <RequestId>a8dec8b3-33a4-11df-8963-01868b7c937a</RequestId>
64+ </ResponseMetadata>
65+</CreateTopicResponse>
66+`
67+
68+var TestDeleteTopicXmlOK = `
69+<DeleteTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
70+ <ResponseMetadata>
71+ <RequestId>f3aa9ac9-3c3d-11df-8235-9dab105e9c32</RequestId>
72+ </ResponseMetadata>
73+</DeleteTopicResponse>
74+`
75+
76+var TestListSubscriptionsXmlOK = `
77+<ListSubscriptionsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
78+ <ListSubscriptionsResult>
79+ <Subscriptions>
80+ <member>
81+ <TopicArn>arn:aws:sns:us-east-1:698519295917:My-Topic</TopicArn>
82+ <Protocol>email</Protocol>
83+ <SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn>
84+ <Owner>123456789012</Owner>
85+ <Endpoint>example@amazon.com</Endpoint>
86+ </member>
87+ </Subscriptions>
88+ </ListSubscriptionsResult>
89+ <ResponseMetadata>
90+ <RequestId>384ac68d-3775-11df-8963-01868b7c937a</RequestId>
91+ </ResponseMetadata>
92+</ListSubscriptionsResponse>
93+`
94+
95+var TestGetTopicAttributesXmlOK = `
96+<GetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
97+ <GetTopicAttributesResult>
98+ <Attributes>
99+ <entry>
100+ <key>Owner</key>
101+ <value>123456789012</value>
102+ </entry>
103+ <entry>
104+ <key>Policy</key>
105+ <value>{"Version":"2008-10-17","Id":"us-east-1/698519295917/test__default_policy_ID","Statement" : [{"Effect":"Allow","Sid":"us-east-1/698519295917/test__default_statement_ID","Principal" : {"AWS": "*"},"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish","SNS:Receive"],"Resource":"arn:aws:sns:us-east-1:698519295917:test","Condition" : {"StringLike" : {"AWS:SourceArn": "arn:aws:*:*:698519295917:*"}}}]}</value>
106+ </entry>
107+ <entry>
108+ <key>TopicArn</key>
109+ <value>arn:aws:sns:us-east-1:123456789012:My-Topic</value>
110+ </entry>
111+ </Attributes>
112+ </GetTopicAttributesResult>
113+ <ResponseMetadata>
114+ <RequestId>057f074c-33a7-11df-9540-99d0768312d3</RequestId>
115+ </ResponseMetadata>
116+</GetTopicAttributesResponse>
117+`
118+
119+var TestPublishXmlOK = `
120+<PublishResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
121+ <PublishResult>
122+ <MessageId>94f20ce6-13c5-43a0-9a9e-ca52d816e90b</MessageId>
123+ </PublishResult>
124+ <ResponseMetadata>
125+ <RequestId>f187a3c1-376f-11df-8963-01868b7c937a</RequestId>
126+ </ResponseMetadata>
127+</PublishResponse>
128+`
129+
130+var TestSetTopicAttributesXmlOK = `
131+<SetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
132+ <ResponseMetadata>
133+ <RequestId>a8763b99-33a7-11df-a9b7-05d48da6f042</RequestId>
134+ </ResponseMetadata>
135+</SetTopicAttributesResponse>
136+`
137+
138+var TestSubscribeXmlOK = `
139+<SubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
140+ <SubscribeResult>
141+ <SubscriptionArn>pending confirmation</SubscriptionArn>
142+ </SubscribeResult>
143+ <ResponseMetadata>
144+ <RequestId>a169c740-3766-11df-8963-01868b7c937a</RequestId>
145+ </ResponseMetadata>
146+</SubscribeResponse>
147+`
148+
149+var TestUnsubscribeXmlOK = `
150+<UnsubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
151+ <ResponseMetadata>
152+ <RequestId>18e0ac39-3776-11df-84c0-b93cc1666b84</RequestId>
153+ </ResponseMetadata>
154+</UnsubscribeResponse>
155+`
156+
157+var TestConfirmSubscriptionXmlOK = `
158+<ConfirmSubscriptionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
159+ <ConfirmSubscriptionResult>
160+ <SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn>
161+ </ConfirmSubscriptionResult>
162+ <ResponseMetadata>
163+ <RequestId>7a50221f-3774-11df-a9b7-05d48da6f042</RequestId>
164+ </ResponseMetadata>
165+</ConfirmSubscriptionResponse>
166+`
167+
168+var TestAddPermissionXmlOK = `
169+<AddPermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
170+ <ResponseMetadata>
171+ <RequestId>6a213e4e-33a8-11df-9540-99d0768312d3</RequestId>
172+ </ResponseMetadata>
173+</AddPermissionResponse>
174+`
175+
176+var TestRemovePermissionXmlOK = `
177+<RemovePermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
178+ <ResponseMetadata>
179+ <RequestId>d170b150-33a8-11df-995a-2d6fbe836cc1</RequestId>
180+ </ResponseMetadata>
181+</RemovePermissionResponse>
182+`
183+
184+var TestListSubscriptionsByTopicXmlOK = `
185+<ListSubscriptionsByTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
186+ <ListSubscriptionsByTopicResult>
187+ <Subscriptions>
188+ <member>
189+ <TopicArn>arn:aws:sns:us-east-1:123456789012:My-Topic</TopicArn>
190+ <Protocol>email</Protocol>
191+ <SubscriptionArn>arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca</SubscriptionArn>
192+ <Owner>123456789012</Owner>
193+ <Endpoint>example@amazon.com</Endpoint>
194+ </member>
195+ </Subscriptions>
196+ </ListSubscriptionsByTopicResult>
197+ <ResponseMetadata>
198+ <RequestId>b9275252-3774-11df-9540-99d0768312d3</RequestId>
199+ </ResponseMetadata>
200+</ListSubscriptionsByTopicResponse>
201+`
202
203=== added file 'sign.go'
204--- sign.go 1970-01-01 00:00:00 +0000
205+++ sign.go 2011-09-21 19:16:26 +0000
206@@ -0,0 +1,64 @@
207+package sns
208+
209+import (
210+ "crypto/hmac"
211+ "encoding/base64"
212+ "launchpad.net/goamz/aws"
213+ "sort"
214+ "strings"
215+)
216+
217+var b64 = base64.StdEncoding
218+/*
219+func sign(auth aws.Auth, method, path string, params url.Values, headers http.Header) {
220+ var host string
221+ for k, v := range headers {
222+ k = strings.ToLower(k)
223+ switch k {
224+ case "host":
225+ host = v[0]
226+ }
227+ }
228+
229+ params["AWSAccessKeyId"] = []string{auth.AccessKey}
230+ params["SignatureVersion"] = []string{"2"}
231+ params["SignatureMethod"] = []string{"HmacSHA256"}
232+
233+ var sarry []string
234+ for k, v := range params {
235+ sarry = append(sarry, aws.Encode(k) + "=" + aws.Encode(v[0]))
236+ }
237+
238+ sort.StringSlice(sarry).Sort()
239+ joined := strings.Join(sarry, "&")
240+
241+ payload := strings.Join([]string{method, host, "/", joined}, "\n")
242+ hash := hmac.NewSHA256([]byte(auth.SecretKey))
243+ hash.Write([]byte(payload))
244+ signature := make([]byte, b64.EncodedLen(hash.Size()))
245+ b64.Encode(signature, hash.Sum())
246+
247+ params["Signature"] = []string{"AWS " + string(signature)}
248+ println("Payload:", payload)
249+ println("Signature:", strings.Join(params["Signature"], "|"))
250+}*/
251+
252+func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
253+ params["AWSAccessKeyId"] = auth.AccessKey
254+ params["SignatureVersion"] = "2"
255+ params["SignatureMethod"] = "HmacSHA256"
256+
257+ var sarray []string
258+ for k, v := range params {
259+ sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v))
260+ }
261+ sort.StringSlice(sarray).Sort()
262+ joined := strings.Join(sarray, "&")
263+ payload := method + "\n" + host + "\n" + path + "\n" + joined
264+ hash := hmac.NewSHA256([]byte(auth.SecretKey))
265+ hash.Write([]byte(payload))
266+ signature := make([]byte, b64.EncodedLen(hash.Size()))
267+ b64.Encode(signature, hash.Sum())
268+
269+ params["Signature"] = string(signature)
270+}
271
272=== added file 'sns.go'
273--- sns.go 1970-01-01 00:00:00 +0000
274+++ sns.go 2011-09-21 19:16:26 +0000
275@@ -0,0 +1,428 @@
276+//
277+// goamz - Go packages to interact with the Amazon Web Services.
278+//
279+// https://wiki.ubuntu.com/goamz
280+//
281+// Copyright (c) 2011 Memeo Inc.
282+//
283+// Written by Prudhvi Krishna Surapaneni <me@prudhvi.net>
284+//
285+package sns
286+
287+import (
288+ "launchpad.net/goamz/aws"
289+ "os"
290+ "http"
291+ "time"
292+ "url"
293+ "xml"
294+ "strconv"
295+)
296+
297+// The SNS type encapsulates operation with an SNS region.
298+type SNS struct {
299+ aws.Auth
300+ aws.Region
301+ private byte // Reserve the right of using private data.
302+}
303+
304+type Topic struct {
305+ *SNS
306+ TopicArn string
307+}
308+
309+func New(auth aws.Auth, region aws.Region) *SNS {
310+ return &SNS{auth, region, 0}
311+}
312+
313+type Message struct {
314+ *SNS
315+ *Topic
316+ Message [8192]byte
317+ Subject string
318+}
319+
320+type Subscription struct {
321+ Endpoint string
322+ Owner string
323+ Protocol string
324+ SubscriptionArn string
325+ TopicArn string
326+}
327+
328+func (topic *Topic) Message(message [8192]byte, subject string) *Message {
329+ return &Message{topic.SNS, topic, message, subject}
330+}
331+
332+type ResponseMetadata struct {
333+ RequestId string
334+ BoxUsage float64
335+}
336+
337+type ListTopicsResponse struct {
338+ Topics []Topic `xml:"ListTopicsResult>Topics>member"`
339+ NextToken string
340+ ResponseMetadata
341+}
342+
343+type CreateTopicResponse struct {
344+ Topic Topic `xml:"CreateTopicResult>"`
345+ ResponseMetadata
346+}
347+
348+type DeleteTopicResponse struct {
349+ ResponseMetadata
350+}
351+
352+type ListSubscriptionsResponse struct {
353+ Subscriptions []Subscription `xml:"ListSubscriptionsResult>Subscriptions>member"`
354+ NextToken string
355+ ResponseMetadata
356+}
357+
358+type AttributeEntry struct {
359+ Key, Value string
360+}
361+
362+type GetTopicAttributesResponse struct {
363+ Attributes []AttributeEntry `xml:"GetTopicAttributesResult>Attributes>entry"`
364+ ResponseMetadata
365+}
366+
367+func makeParams(action string) map[string]string {
368+ params := make(map[string]string)
369+ params["Action"] = action
370+ return params
371+}
372+
373+// ListTopics
374+//
375+// See http://goo.gl/lfrMK for more details.
376+func (sns *SNS) ListTopics(NextToken *string) (resp *ListTopicsResponse, err os.Error) {
377+ resp = &ListTopicsResponse{}
378+ params := makeParams("ListTopics")
379+ if NextToken != nil {
380+ params["NextToken"] = *NextToken
381+ }
382+ err = sns.query(nil, nil, params, resp)
383+ return
384+}
385+
386+// CreateTopic
387+//
388+// See http://goo.gl/m9aAt for more details.
389+func (sns *SNS) CreateTopic(Name string) (resp *CreateTopicResponse, err os.Error) {
390+ resp = &CreateTopicResponse{}
391+ params := makeParams("CreateTopic")
392+ params["Name"] = Name
393+ err = sns.query(nil, nil, params, resp)
394+ return
395+}
396+
397+// DeleteTopic
398+//
399+// See http://goo.gl/OXNcY for more details.
400+func (sns *SNS) DeleteTopic(topic Topic) (resp *DeleteTopicResponse, err os.Error) {
401+ resp = &DeleteTopicResponse{}
402+ params := makeParams("DeleteTopic")
403+ params["TopicArn"] = topic.TopicArn
404+ err = sns.query(nil, nil, params, resp)
405+ return
406+}
407+
408+// Delete
409+//
410+// Helper function for deleting a topic
411+func (topic *Topic) Delete() (resp *DeleteTopicResponse, err os.Error) {
412+ return topic.SNS.DeleteTopic(*topic)
413+}
414+
415+// ListSubscriptions
416+//
417+// See http://goo.gl/k3aGn for more details.
418+func (sns *SNS) ListSubscriptions(NextToken *string) (resp *ListSubscriptionsResponse, err os.Error) {
419+ resp = &ListSubscriptionsResponse{}
420+ params := makeParams("ListSubscriptions")
421+ if NextToken != nil {
422+ params["NextToken"] = *NextToken
423+ }
424+ err = sns.query(nil, nil, params, resp)
425+ return
426+}
427+
428+// GetTopicAttributes
429+//
430+// See http://goo.gl/WXRoX for more details.
431+func (sns *SNS) GetTopicAttributes(TopicArn string) (resp *GetTopicAttributesResponse, err os.Error) {
432+ resp = &GetTopicAttributesResponse{}
433+ params := makeParams("GetTopicAttributes")
434+ params["TopicArn"] = TopicArn
435+ err = sns.query(nil, nil, params, resp)
436+ return
437+}
438+
439+type PublishOpt struct {
440+ Message string
441+ MessageStructure string
442+ Subject string
443+ TopicArn string
444+}
445+
446+type PublishResponse struct {
447+ MessageId string `xml:"PublishResult>MessageId"`
448+ ResponseMetadata
449+}
450+
451+// Publish
452+//
453+// See http://goo.gl/AY2D8 for more details.
454+func (sns *SNS) Publish(options *PublishOpt) (resp *PublishResponse, err os.Error) {
455+ resp = &PublishResponse{}
456+ params := makeParams("Publish")
457+
458+ if options.Subject != "" {
459+ params["Subject"] = options.Subject
460+ }
461+
462+ if options.MessageStructure != "" {
463+ params["MessageStructure"] = options.MessageStructure
464+ }
465+
466+ if options.Message != "" {
467+ params["Message"] = options.Message
468+ }
469+
470+ if options.TopicArn != "" {
471+ params["TopicArn"] = options.TopicArn
472+ }
473+
474+ err = sns.query(nil, nil, params, resp)
475+ return
476+}
477+
478+type SetTopicAttributesResponse struct {
479+ ResponseMetadata
480+}
481+
482+// SetTopicAttributes
483+//
484+// See http://goo.gl/oVYW7 for more details.
485+func (sns *SNS) SetTopicAttributes(AttributeName, AttributeValue, TopicArn string) (resp *SetTopicAttributesResponse, err os.Error) {
486+ resp = &SetTopicAttributesResponse{}
487+ params := makeParams("SetTopicAttributes")
488+
489+ if AttributeName == "" || TopicArn == "" {
490+ return nil, os.NewError("Invalid Attribute Name or TopicArn")
491+ }
492+
493+ params["AttributeName"] = AttributeName
494+ params["AttributeValue"] = AttributeValue
495+ params["TopicArn"] = TopicArn
496+
497+ err = sns.query(nil, nil, params, resp)
498+ return
499+}
500+
501+type SubscribeResponse struct {
502+ SubscriptionArn string `xml:"SubscribeResult>SubscriptionArn"`
503+ ResponseMetadata
504+}
505+
506+// Subscribe
507+//
508+// See http://goo.gl/c3iGS for more details.
509+func (sns *SNS) Subscribe(Endpoint, Protocol, TopicArn string) (resp *SubscribeResponse, err os.Error) {
510+ resp = &SubscribeResponse{}
511+ params := makeParams("Subscribe")
512+
513+ params["Endpoint"] = Endpoint
514+ params["Protocol"] = Protocol
515+ params["TopicArn"] = TopicArn
516+
517+ err = sns.query(nil, nil, params, resp)
518+ return
519+}
520+
521+type UnsubscribeResponse struct {
522+ ResponseMetadata
523+}
524+
525+// Unsubscribe
526+//
527+// See http://goo.gl/4l5Ge for more details.
528+func (sns *SNS) Unsubscribe(SubscriptionArn string) (resp *UnsubscribeResponse, err os.Error) {
529+ resp = &UnsubscribeResponse{}
530+ params := makeParams("Unsubscribe")
531+
532+ params["SubscriptionArn"] = SubscriptionArn
533+
534+ err = sns.query(nil, nil, params, resp)
535+ return
536+}
537+
538+type ConfirmSubscriptionResponse struct {
539+ SubscriptionArn string `xml:"ConfirmSubscriptionResult>SubscriptionArn"`
540+ ResponseMetadata
541+}
542+
543+type ConfirmSubscriptionOpt struct {
544+ AuthenticateOnUnsubscribe string
545+ Token string
546+ TopicArn string
547+}
548+
549+// ConfirmSubscription
550+//
551+// See http://goo.gl/3hXzH for more details.
552+func (sns *SNS) ConfirmSubscription(options *ConfirmSubscriptionOpt) (resp *ConfirmSubscriptionResponse, err os.Error) {
553+ resp = &ConfirmSubscriptionResponse{}
554+ params := makeParams("ConfirmSubscription")
555+
556+ if options.AuthenticateOnUnsubscribe != "" {
557+ params["AuthenticateOnUnsubscribe"] = options.AuthenticateOnUnsubscribe
558+ }
559+
560+ params["Token"] = options.Token
561+ params["TopicArn"] = options.TopicArn
562+
563+ err = sns.query(nil, nil, params, resp)
564+ return
565+}
566+
567+type Permission struct {
568+ ActionName string
569+ AccountId string
570+}
571+
572+type AddPermissionResponse struct {
573+ ResponseMetadata
574+}
575+
576+// AddPermission
577+//
578+// See http://goo.gl/mbY4a for more details.
579+func (sns *SNS) AddPermission(permissions []Permission, Label, TopicArn string) (resp *AddPermissionResponse, err os.Error) {
580+ resp = &AddPermissionResponse{}
581+ params := makeParams("AddPermission")
582+
583+ for i, p := range permissions {
584+ params["AWSAccountId.member."+strconv.Itoa(i+1)] = p.AccountId
585+ params["ActionName.member."+strconv.Itoa(i+1)] = p.ActionName
586+ }
587+
588+ params["Label"] = Label
589+ params["TopicArn"] = TopicArn
590+
591+ err = sns.query(nil, nil, params, resp)
592+ return
593+}
594+
595+type RemovePermissionResponse struct {
596+ ResponseMetadata
597+}
598+
599+// RemovePermission
600+//
601+// See http://goo.gl/wGl5j for more details.
602+func (sns *SNS) RemovePermission(Label, TopicArn string) (resp *RemovePermissionResponse, err os.Error) {
603+ resp = &RemovePermissionResponse{}
604+ params := makeParams("RemovePermission")
605+
606+ params["Label"] = Label
607+ params["TopicArn"] = TopicArn
608+
609+ err = sns.query(nil, nil, params, resp)
610+ return
611+}
612+
613+type ListSubscriptionByTopicResponse struct {
614+ Subscriptions []Subscription `xml:"ListSubscriptionsByTopicResult>Subscriptions>member"`
615+ ResponseMetadata
616+}
617+
618+type ListSubscriptionByTopicOpt struct {
619+ NextToken string
620+ TopicArn string
621+}
622+
623+// ListSubscriptionByTopic
624+//
625+// See http://goo.gl/LaVcC for more details.
626+func (sns *SNS) ListSubscriptionByTopic(options *ListSubscriptionByTopicOpt) (resp *ListSubscriptionByTopicResponse, err os.Error) {
627+ resp = &ListSubscriptionByTopicResponse{}
628+ params := makeParams("ListSbubscriptionByTopic")
629+
630+ if options.NextToken != "" {
631+ params["NextToken"] = options.NextToken
632+ }
633+
634+ params["TopicArn"] = options.TopicArn
635+
636+ err = sns.query(nil, nil, params, resp)
637+ return
638+}
639+
640+type Error struct {
641+ StatusCode int
642+ Code string
643+ Message string
644+ RequestId string
645+}
646+
647+func (err *Error) String() string {
648+ return err.Message
649+}
650+
651+type xmlErrors struct {
652+ RequestId string
653+ Errors []Error `xml:"Errors>Error"`
654+}
655+
656+func (sns *SNS) query(topic *Topic, message *Message, params map[string]string, resp interface{}) os.Error {
657+ params["Timestamp"] = time.UTC().Format(time.RFC3339)
658+ url_, err := url.Parse(sns.Region.SNSEndpoint)
659+ if err != nil {
660+ return err
661+ }
662+
663+ sign(sns.Auth, "GET", "/", params, url_.Host)
664+ url_.RawQuery = multimap(params).Encode()
665+ r, err := http.Get(url_.String())
666+ if err != nil {
667+ return err
668+ }
669+ defer r.Body.Close()
670+
671+ //dump, _ := http.DumpResponse(r, true)
672+ //println("DUMP:\n", string(dump))
673+ //return nil
674+
675+ if r.StatusCode != 200 {
676+ return buildError(r)
677+ }
678+ err = xml.Unmarshal(r.Body, resp)
679+ return err
680+}
681+
682+func buildError(r *http.Response) os.Error {
683+ errors := xmlErrors{}
684+ xml.Unmarshal(r.Body, &errors)
685+ var err Error
686+ if len(errors.Errors) > 0 {
687+ err = errors.Errors[0]
688+ }
689+ err.RequestId = errors.RequestId
690+ err.StatusCode = r.StatusCode
691+ if err.Message == "" {
692+ err.Message = r.Status
693+ }
694+ return &err
695+}
696+
697+func multimap(p map[string]string) url.Values {
698+ q := make(url.Values, len(p))
699+ for k, v := range p {
700+ q[k] = []string{v}
701+ }
702+ return q
703+}
704
705=== added file 'sns_test.go'
706--- sns_test.go 1970-01-01 00:00:00 +0000
707+++ sns_test.go 2011-09-21 19:16:26 +0000
708@@ -0,0 +1,230 @@
709+package sns_test
710+
711+import (
712+ "launchpad.net/gocheck"
713+ "launchpad.net/goamz/aws"
714+ "launchpad.net/goamz/sns"
715+)
716+
717+var _ = gocheck.Suite(&S{})
718+
719+type S struct {
720+ HTTPSuite
721+ sns *sns.SNS
722+}
723+
724+func (s *S) SetUpSuite(c *gocheck.C) {
725+ s.HTTPSuite.SetUpSuite(c)
726+ auth := aws.Auth{"abc", "123"}
727+ s.sns = sns.New(auth, aws.Region{SNSEndpoint: testServer.URL})
728+}
729+
730+func (s *S) TestListTopicsOK(c *gocheck.C) {
731+ testServer.PrepareResponse(200, nil, TestListTopicsXmlOK)
732+
733+ resp, err := s.sns.ListTopics(nil)
734+ req := testServer.WaitRequest()
735+
736+ c.Assert(req.Method, gocheck.Equals, "GET")
737+ c.Assert(req.URL.Path, gocheck.Equals, "/")
738+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
739+
740+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "bd10b26c-e30e-11e0-ba29-93c3aca2f103")
741+ c.Assert(err, gocheck.IsNil)
742+}
743+
744+func (s *S) TestCreateTopic(c *gocheck.C) {
745+ testServer.PrepareResponse(200, nil, TestCreateTopicXmlOK)
746+
747+ resp, err := s.sns.CreateTopic("My-Topic")
748+ req := testServer.WaitRequest()
749+
750+ c.Assert(req.Method, gocheck.Equals, "GET")
751+ c.Assert(req.URL.Path, gocheck.Equals, "/")
752+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
753+
754+ c.Assert(resp.Topic.TopicArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic")
755+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "a8dec8b3-33a4-11df-8963-01868b7c937a")
756+ c.Assert(err, gocheck.IsNil)
757+}
758+
759+func (s *S) TestDeleteTopic(c *gocheck.C) {
760+ testServer.PrepareResponse(200, nil, TestDeleteTopicXmlOK)
761+
762+ t := sns.Topic{nil, "arn:aws:sns:us-east-1:123456789012:My-Topic"}
763+ resp, err := s.sns.DeleteTopic(t)
764+ req := testServer.WaitRequest()
765+
766+ c.Assert(req.Method, gocheck.Equals, "GET")
767+ c.Assert(req.URL.Path, gocheck.Equals, "/")
768+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
769+
770+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "f3aa9ac9-3c3d-11df-8235-9dab105e9c32")
771+ c.Assert(err, gocheck.IsNil)
772+}
773+
774+func (s *S) TestListSubscriptions(c *gocheck.C) {
775+ testServer.PrepareResponse(200, nil, TestListSubscriptionsXmlOK)
776+
777+ resp, err := s.sns.ListSubscriptions(nil)
778+ req := testServer.WaitRequest()
779+
780+ c.Assert(req.Method, gocheck.Equals, "GET")
781+ c.Assert(req.URL.Path, gocheck.Equals, "/")
782+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
783+
784+ c.Assert(len(resp.Subscriptions), gocheck.Not(gocheck.Equals), 0)
785+ c.Assert(resp.Subscriptions[0].Protocol, gocheck.Equals, "email")
786+ c.Assert(resp.Subscriptions[0].Endpoint, gocheck.Equals, "example@amazon.com")
787+ c.Assert(resp.Subscriptions[0].SubscriptionArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca")
788+ c.Assert(resp.Subscriptions[0].TopicArn, gocheck.Equals, "arn:aws:sns:us-east-1:698519295917:My-Topic")
789+ c.Assert(resp.Subscriptions[0].Owner, gocheck.Equals, "123456789012")
790+ c.Assert(err, gocheck.IsNil)
791+}
792+
793+func (s *S) TestGetTopicAttributes(c *gocheck.C) {
794+ testServer.PrepareResponse(200, nil, TestGetTopicAttributesXmlOK)
795+
796+ resp, err := s.sns.GetTopicAttributes("arn:aws:sns:us-east-1:123456789012:My-Topic")
797+ req := testServer.WaitRequest()
798+
799+ c.Assert(req.Method, gocheck.Equals, "GET")
800+ c.Assert(req.URL.Path, gocheck.Equals, "/")
801+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
802+
803+ c.Assert(len(resp.Attributes), gocheck.Not(gocheck.Equals), 0)
804+ c.Assert(resp.Attributes[0].Key, gocheck.Equals, "Owner")
805+ c.Assert(resp.Attributes[0].Value, gocheck.Equals, "123456789012")
806+ c.Assert(resp.Attributes[1].Key, gocheck.Equals, "Policy")
807+ c.Assert(resp.Attributes[1].Value, gocheck.Equals, `{"Version":"2008-10-17","Id":"us-east-1/698519295917/test__default_policy_ID","Statement" : [{"Effect":"Allow","Sid":"us-east-1/698519295917/test__default_statement_ID","Principal" : {"AWS": "*"},"Action":["SNS:GetTopicAttributes","SNS:SetTopicAttributes","SNS:AddPermission","SNS:RemovePermission","SNS:DeleteTopic","SNS:Subscribe","SNS:ListSubscriptionsByTopic","SNS:Publish","SNS:Receive"],"Resource":"arn:aws:sns:us-east-1:698519295917:test","Condition" : {"StringLike" : {"AWS:SourceArn": "arn:aws:*:*:698519295917:*"}}}]}`)
808+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "057f074c-33a7-11df-9540-99d0768312d3")
809+ c.Assert(err, gocheck.IsNil)
810+}
811+
812+func (s *S) TestPublish(c *gocheck.C) {
813+ testServer.PrepareResponse(200, nil, TestPublishXmlOK)
814+
815+ pubOpt := &sns.PublishOpt{"foobar","","subject","arn:aws:sns:us-east-1:123456789012:My-Topic"}
816+ resp, err := s.sns.Publish(pubOpt)
817+ req := testServer.WaitRequest()
818+
819+ c.Assert(req.Method, gocheck.Equals, "GET")
820+ c.Assert(req.URL.Path, gocheck.Equals, "/")
821+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
822+
823+ c.Assert(resp.MessageId, gocheck.Equals, "94f20ce6-13c5-43a0-9a9e-ca52d816e90b")
824+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "f187a3c1-376f-11df-8963-01868b7c937a")
825+ c.Assert(err, gocheck.IsNil)
826+}
827+
828+func (s *S) TestSetTopicAttributes(c *gocheck.C) {
829+ testServer.PrepareResponse(200, nil, TestSetTopicAttributesXmlOK)
830+
831+ resp, err := s.sns.SetTopicAttributes("DisplayName", "MyTopicName", "arn:aws:sns:us-east-1:123456789012:My-Topic")
832+ req := testServer.WaitRequest()
833+
834+ c.Assert(req.Method, gocheck.Equals, "GET")
835+ c.Assert(req.URL.Path, gocheck.Equals, "/")
836+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
837+
838+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "a8763b99-33a7-11df-a9b7-05d48da6f042")
839+ c.Assert(err, gocheck.IsNil)
840+}
841+
842+func (s *S) TestSubscribe(c *gocheck.C) {
843+ testServer.PrepareResponse(200, nil, TestSubscribeXmlOK)
844+
845+ resp, err := s.sns.Subscribe("example@amazon.com", "email", "arn:aws:sns:us-east-1:123456789012:My-Topic")
846+ req := testServer.WaitRequest()
847+
848+ c.Assert(req.Method, gocheck.Equals, "GET")
849+ c.Assert(req.URL.Path, gocheck.Equals, "/")
850+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
851+
852+ c.Assert(resp.SubscriptionArn, gocheck.Equals, "pending confirmation")
853+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "a169c740-3766-11df-8963-01868b7c937a")
854+ c.Assert(err, gocheck.IsNil)
855+}
856+
857+func (s *S) TestUnsubscribe(c *gocheck.C) {
858+ testServer.PrepareResponse(200, nil, TestUnsubscribeXmlOK)
859+
860+ resp, err := s.sns.Unsubscribe("arn:aws:sns:us-east-1:123456789012:My-Topic:a169c740-3766-11df-8963-01868b7c937a")
861+ req := testServer.WaitRequest()
862+
863+ c.Assert(req.Method, gocheck.Equals, "GET")
864+ c.Assert(req.URL.Path, gocheck.Equals, "/")
865+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
866+
867+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "18e0ac39-3776-11df-84c0-b93cc1666b84")
868+ c.Assert(err, gocheck.IsNil)
869+}
870+
871+func (s *S) TestConfirmSubscription(c *gocheck.C) {
872+ testServer.PrepareResponse(200, nil, TestConfirmSubscriptionXmlOK)
873+
874+ opt := &sns.ConfirmSubscriptionOpt{"", "51b2ff3edb475b7d91550e0ab6edf0c1de2a34e6ebaf6", "arn:aws:sns:us-east-1:123456789012:My-Topic"}
875+ resp, err := s.sns.ConfirmSubscription(opt)
876+ req := testServer.WaitRequest()
877+
878+ c.Assert(req.Method, gocheck.Equals, "GET")
879+ c.Assert(req.URL.Path, gocheck.Equals, "/")
880+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
881+
882+ c.Assert(resp.SubscriptionArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca")
883+ c.Assert(resp.ResponseMetadata.RequestId, gocheck.Equals, "7a50221f-3774-11df-a9b7-05d48da6f042")
884+ c.Assert(err, gocheck.IsNil)
885+}
886+
887+func (s *S) TestAddPermission(c *gocheck.C) {
888+ testServer.PrepareResponse(200, nil, TestAddPermissionXmlOK)
889+ perm := make([]sns.Permission,2)
890+ perm[0].ActionName = "Publish"
891+ perm[1].ActionName = "GetTopicAttributes"
892+ perm[0].AccountId = "987654321000"
893+ perm[1].AccountId = "876543210000"
894+
895+ resp, err := s.sns.AddPermission(perm, "NewPermission", "arn:aws:sns:us-east-1:123456789012:My-Topic")
896+ req := testServer.WaitRequest()
897+
898+ c.Assert(req.Method, gocheck.Equals, "GET")
899+ c.Assert(req.URL.Path, gocheck.Equals, "/")
900+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
901+
902+ c.Assert(resp.RequestId, gocheck.Equals, "6a213e4e-33a8-11df-9540-99d0768312d3")
903+ c.Assert(err, gocheck.IsNil)
904+}
905+
906+func (s *S) TestRemovePermission(c *gocheck.C) {
907+ testServer.PrepareResponse(200, nil, TestRemovePermissionXmlOK)
908+
909+ resp, err := s.sns.RemovePermission("NewPermission", "arn:aws:sns:us-east-1:123456789012:My-Topic")
910+ req := testServer.WaitRequest()
911+
912+ c.Assert(req.Method, gocheck.Equals, "GET")
913+ c.Assert(req.URL.Path, gocheck.Equals, "/")
914+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
915+
916+ c.Assert(resp.RequestId, gocheck.Equals, "d170b150-33a8-11df-995a-2d6fbe836cc1")
917+ c.Assert(err, gocheck.IsNil)
918+}
919+
920+func (s *S) TestListSubscriptionByTopic(c *gocheck.C) {
921+ testServer.PrepareResponse(200, nil, TestListSubscriptionsByTopicXmlOK)
922+
923+ opt := &sns.ListSubscriptionByTopicOpt{"", "arn:aws:sns:us-east-1:123456789012:My-Topic"}
924+ resp, err := s.sns.ListSubscriptionByTopic(opt)
925+ req := testServer.WaitRequest()
926+
927+ c.Assert(req.Method, gocheck.Equals, "GET")
928+ c.Assert(req.URL.Path, gocheck.Equals, "/")
929+ c.Assert(req.Header["Date"], gocheck.Not(gocheck.Equals), "")
930+
931+ c.Assert(len(resp.Subscriptions), gocheck.Not(gocheck.Equals), 0)
932+ c.Assert(resp.Subscriptions[0].TopicArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic")
933+ c.Assert(resp.Subscriptions[0].SubscriptionArn, gocheck.Equals, "arn:aws:sns:us-east-1:123456789012:My-Topic:80289ba6-0fd4-4079-afb4-ce8c8260f0ca")
934+ c.Assert(resp.Subscriptions[0].Owner, gocheck.Equals, "123456789012")
935+ c.Assert(resp.Subscriptions[0].Endpoint, gocheck.Equals, "example@amazon.com")
936+ c.Assert(resp.Subscriptions[0].Protocol, gocheck.Equals, "email")
937+ c.Assert(err, gocheck.IsNil)
938+}
939
940=== added file 'suite_test.go'
941--- suite_test.go 1970-01-01 00:00:00 +0000
942+++ suite_test.go 2011-09-21 19:16:26 +0000
943@@ -0,0 +1,139 @@
944+package sns_test
945+
946+import (
947+ "flag"
948+ "fmt"
949+ . "launchpad.net/gocheck"
950+ "http"
951+ "launchpad.net/goamz/aws"
952+ "os"
953+ "testing"
954+ "time"
955+ "url"
956+)
957+
958+func Test(t *testing.T) {
959+ TestingT(t)
960+}
961+
962+var integration = flag.Bool("i", false, "Enable integration tests")
963+
964+type SuiteI struct {
965+ auth aws.Auth
966+}
967+
968+func (s *SuiteI) SetUpSuite(c *C) {
969+ if !*integration {
970+ c.Skip("Integration tests not enabled (-i flag)")
971+ }
972+ auth, err := aws.EnvAuth()
973+ if err != nil {
974+ c.Fatal(err.String())
975+ }
976+ s.auth = auth
977+}
978+
979+type HTTPSuite struct{}
980+
981+var testServer = NewTestHTTPServer("http://localhost:4444", 5e9)
982+
983+func (s *HTTPSuite) SetUpSuite(c *C) {
984+ testServer.Start()
985+}
986+
987+func (s *HTTPSuite) TearDownTest(c *C) {
988+ testServer.FlushRequests()
989+}
990+
991+type TestHTTPServer struct {
992+ URL string
993+ Timeout int64
994+ started bool
995+ request chan *http.Request
996+ response chan *testResponse
997+ pending chan bool
998+}
999+
1000+type testResponse struct {
1001+ Status int
1002+ Headers map[string]string
1003+ Body string
1004+}
1005+
1006+func NewTestHTTPServer(url string, timeout int64) *TestHTTPServer {
1007+ return &TestHTTPServer{URL: url, Timeout: timeout}
1008+}
1009+
1010+func (s *TestHTTPServer) Start() {
1011+ if s.started {
1012+ return
1013+ }
1014+ s.started = true
1015+
1016+ s.request = make(chan *http.Request, 64)
1017+ s.response = make(chan *testResponse, 64)
1018+ s.pending = make(chan bool, 64)
1019+
1020+ url, _ := url.Parse(s.URL)
1021+ go http.ListenAndServe(url.Host, s)
1022+
1023+ s.PrepareResponse(202, nil, "Nothing.")
1024+ for {
1025+ // Wait for it to be up.
1026+ resp, err := http.Get(s.URL)
1027+ if err == nil && resp.StatusCode == 202 {
1028+ break
1029+ }
1030+ fmt.Fprintf(os.Stderr, "\nWaiting for fake server to be up... ")
1031+ time.Sleep(1e8)
1032+ }
1033+ fmt.Fprintf(os.Stderr, "done\n\n")
1034+ s.WaitRequest() // Consume dummy request.
1035+}
1036+
1037+// FlushRequests discards requests which were not yet consumed by WaitRequest.
1038+func (s *TestHTTPServer) FlushRequests() {
1039+ for {
1040+ select {
1041+ case <-s.request:
1042+ default:
1043+ return
1044+ }
1045+ }
1046+}
1047+
1048+func (s *TestHTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1049+ s.request <- req
1050+ var resp *testResponse
1051+ select {
1052+ case resp = <-s.response:
1053+ case <-time.After(s.Timeout):
1054+ fmt.Fprintf(os.Stderr, "ERROR: Timeout waiting for test to provide response\n")
1055+ resp = &testResponse{500, nil, ""}
1056+ }
1057+ if resp.Headers != nil {
1058+ h := w.Header()
1059+ for k, v := range resp.Headers {
1060+ h.Set(k, v)
1061+ }
1062+ }
1063+ if resp.Status != 0 {
1064+ w.WriteHeader(resp.Status)
1065+ }
1066+ w.Write([]byte(resp.Body))
1067+}
1068+
1069+func (s *TestHTTPServer) WaitRequest() *http.Request {
1070+ select {
1071+ case req := <-s.request:
1072+ req.ParseForm()
1073+ return req
1074+ case <-time.After(s.Timeout):
1075+ panic("Timeout waiting for goamz request")
1076+ }
1077+ panic("unreached")
1078+}
1079+
1080+func (s *TestHTTPServer) PrepareResponse(status int, headers map[string]string, body string) {
1081+ s.response <- &testResponse{status, headers, body}
1082+}
1083
1084=== renamed file 'suite_test.go' => 'suite_test.go.moved'

Subscribers

People subscribed via source and target branches