1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +00:00

Compare commits

...

36 Commits

Author SHA1 Message Date
James Hillyerd
3062b70ea0 Merge branch 'release/1.2.0' 2017-12-27 13:29:06 -08:00
James Hillyerd
01d51302c4 Prepare release 1.2.0 2017-12-27 13:18:11 -08:00
James Hillyerd
c750dcff81 Merge branch 'hotfix/build' to prevent dup deploys 2017-12-24 13:40:30 -08:00
James Hillyerd
de75b778c0 Only deploy with one version of Go 2017-12-24 13:37:47 -08:00
James Hillyerd
b28e1d86d8 Include version for final goxc release 2017-12-18 19:15:51 -08:00
James Hillyerd
f4fadd7e44 Docker version will now fall back to commit if no tag 2017-12-18 19:12:47 -08:00
James Hillyerd
28b40eb94d Fetch tags during docker build 2017-12-18 19:12:32 -08:00
James Hillyerd
0f67e51e56 Fix version & date in Docker containers for #64 2017-12-18 19:11:08 -08:00
James Hillyerd
a457b65603 Add cmd/client to release builds 2017-12-17 20:05:07 -08:00
James Hillyerd
890d8e0202 Rename link variables, setup travis tag releases 2017-12-17 19:32:05 -08:00
James Hillyerd
9f6dee640e Customize goreleaser to get a working build 2017-12-17 19:10:59 -08:00
James Hillyerd
095796c8a1 Default config from goreleaser init 2017-12-17 12:33:09 -08:00
James Hillyerd
db358fea8c Merge tag '1.2.0-rc2' into develop 2017-12-15 20:41:02 -08:00
James Hillyerd
86554a63b8 Merge branch 'release/1.2.0-rc2' 2017-12-15 20:39:37 -08:00
James Hillyerd
1efe2ba48f Prepare release 1.2.0-rc2 2017-12-15 20:34:27 -08:00
James Hillyerd
f597687aa3 Update CHANGELOG.md 2017-12-15 20:20:27 -08:00
Carlos Tadeu Panato Junior
6368e3a83b Add option to get the latest message using latest as request parameter (#63) 2017-12-15 17:00:09 -08:00
James Hillyerd
ef17ad9074 Update Docker base to go 1.9 2017-12-14 22:16:24 -08:00
James Hillyerd
7908e41212 Fixes #61 - monitor.history=0 panic 2017-12-14 18:54:22 -08:00
James Hillyerd
a9b174bcb6 Add tl;dr to CONTRIBUTING.md 2017-12-14 18:32:55 -08:00
James Hillyerd
dc0b9b325e Use bash for swaks tests, no pipefail in sh 2017-12-12 19:59:33 -08:00
James Hillyerd
0a967f0f21 Update golang versions 2017-12-12 19:57:20 -08:00
James Hillyerd
304a2260e8 Don't close writers with defer 2017-02-12 16:21:44 -08:00
James Hillyerd
9fc9a333a6 Run travis tests with race detector enabled 2017-02-12 14:53:42 -08:00
James Hillyerd
3e8b914f89 Merge branch 'feature/cmdline' into develop 2017-02-05 15:47:40 -08:00
James Hillyerd
5e94f7b750 Address matching should only apply to address, not name 2017-02-05 15:31:31 -08:00
James Hillyerd
64e75face8 Add maxage flag to match subcommand 2017-02-05 15:15:28 -08:00
James Hillyerd
be4675b374 Add powerful match subcommand to cmdline client
- Multiple output formats
- Signals matches via exit status for shell scripts
- Match against To, From, Subject via regular expressions
- Can optionally delete matched messages
2017-02-05 14:17:47 -08:00
James Hillyerd
6722811425 Beginnings of a command line REST client 2017-02-04 18:21:55 -08:00
James Hillyerd
56cff6296a Update changelog 2017-02-04 18:20:27 -08:00
James Hillyerd
a1e35009e0 Add convenience methods to rest/client types 2017-02-04 16:14:40 -08:00
James Hillyerd
cc0428ab9b Merge branch 'release/1.2.0-rc1' into develop 2017-01-29 13:18:19 -08:00
James Hillyerd
68e35b5eca Merge branch 'release/1.2.0-rc1' 2017-01-29 13:14:55 -08:00
James Hillyerd
5ef3adc88e Merge branch 'release/1.1.0' 2016-09-03 11:27:24 -07:00
James Hillyerd
1742bf9a34 Merge branch 'release/1.1.0-rc2' 2016-03-06 16:47:45 -08:00
James Hillyerd
c2779ff054 Merge branch 'release/1.1.0-rc1' 2016-03-03 21:41:09 -08:00
23 changed files with 849 additions and 60 deletions

5
.gitignore vendored
View File

@@ -25,10 +25,13 @@ _testmain.go
*.swp
*.swo
# our binary
# our binaries
/inbucket
/inbucket.exe
/dist/**
/target/**
/cmd/client/client
/cmd/client/client.exe
# local goxc config
.goxc.local.json

60
.goreleaser.yml Normal file
View File

@@ -0,0 +1,60 @@
project_name: inbucket
release:
github:
owner: jhillyerd
name: inbucket
name_template: '{{.Tag}}'
brew:
commit_author:
name: goreleaserbot
email: goreleaser@carlosbecker.com
install: bin.install ""
builds:
- binary: inbucket
goos:
- darwin
- freebsd
- linux
- windows
goarch:
- amd64
goarm:
- "6"
main: .
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
- binary: client
goos:
- darwin
- freebsd
- linux
- windows
goarch:
- amd64
goarm:
- "6"
main: ./cmd/client
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
archive:
format: tar.gz
wrap_in_directory: true
name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
.Arm }}{{ end }}'
format_overrides:
- goos: windows
format: zip
files:
- LICENSE*
- README*
- CHANGELOG*
- inbucket.bat
- etc/**/*
- themes/**/*
fpm:
bindir: /usr/local/bin
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
dist: dist
sign:
artifacts: none

View File

@@ -7,7 +7,6 @@
"Os": "darwin freebsd linux windows",
"ResourcesInclude": "README*,LICENSE*,CHANGELOG*,inbucket.bat,etc,themes",
"PackageVersion": "1.2.0",
"PrereleaseInfo": "rc1",
"ConfigVersion": "0.9",
"BuildSettings": {
"LdFlagsXVars": {

View File

@@ -1,9 +1,21 @@
language: go
sudo: false
env:
- DEPLOY_WITH_MAJOR="1.9"
before_script:
- go vet ./...
go:
- 1.7.5
- master
- 1.8.x
- 1.9.x
script: go test -race -v ./...
deploy:
provider: script
script: etc/travis-deploy.sh
on:
tags: true
branch: master

View File

@@ -4,6 +4,29 @@ Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
[1.2.0] - 2017-12-27
--------------------
### Changed
- No significant code changes from rc2
[1.2.0-rc2] - 2017-12-15
------------------------
### Added
- `rest/client` types `MessageHeader` and `Message` with convenience methods;
provides a more natural API
- Powerful command line REST
[client](https://github.com/jhillyerd/inbucket/wiki/cmd-client)
- Allow use of `latest` as a message ID in REST calls
### Changed
- `rest/client.NewV1` renamed to `New`
- `rest/client` package now embeds the shared `rest/model` structs into its own
types
- Fixed panic when `monitor.history` set to 0
[1.2.0-rc1] - 2017-01-29
------------------------
@@ -76,6 +99,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
specific message.
[Unreleased]: https://github.com/jhillyerd/inbucket/compare/master...develop
[1.2.0]: https://github.com/jhillyerd/inbucket/compare/1.2.0-rc2...1.2.0
[1.2.0-rc2]: https://github.com/jhillyerd/inbucket/compare/1.2.0-rc1...1.2.0-rc2
[1.2.0-rc1]: https://github.com/jhillyerd/inbucket/compare/1.1.0...1.2.0-rc1
[1.1.0]: https://github.com/jhillyerd/inbucket/compare/1.1.0-rc2...1.1.0
[1.1.0-rc2]: https://github.com/jhillyerd/inbucket/compare/1.1.0-rc1...1.1.0-rc2
@@ -91,7 +116,7 @@ Release Checklist
- Ensure *Unreleased* section is up to date
- Rename *Unreleased* section to release name and date.
- Add new GitHub `/compare` link
3. Update goxc version info: `goxc -wc -pv=1.x.0 -pr=snapshot`
3. Update goxc version info: `goxc -wc -pv=1.x.0 -pr=rc1`
4. Run: `goxc interpolate-source` to update VERSION var
5. Run tests
6. Test cross-compile: `goxc`

View File

@@ -4,6 +4,8 @@ How to Contribute
Inbucket encourages third-party patches. It's valuable to know how other
developers are using the product.
**tl;dr:** File pull requests against the `develop` branch, not `master`!
## Getting Started

View File

@@ -1,7 +1,7 @@
# Docker build file for Inbucket, see https://www.docker.io/
# Inbucket website: http://www.inbucket.org/
FROM golang:1.7-alpine
FROM golang:1.9-alpine
MAINTAINER James Hillyerd, @jameshillyerd
# Configuration (WORKDIR doesn't support env vars)

54
cmd/client/list.go Normal file
View File

@@ -0,0 +1,54 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/google/subcommands"
"github.com/jhillyerd/inbucket/rest/client"
)
type listCmd struct {
mailbox string
}
func (*listCmd) Name() string {
return "list"
}
func (*listCmd) Synopsis() string {
return "list contents of mailbox"
}
func (*listCmd) Usage() string {
return `list <mailbox>:
list message IDs in mailbox
`
}
func (l *listCmd) SetFlags(f *flag.FlagSet) {
}
func (l *listCmd) Execute(
_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
mailbox := f.Arg(0)
if mailbox == "" {
return usage("mailbox required")
}
// Setup rest client
c, err := client.New(baseURL())
if err != nil {
return fatal("Couldn't build client", err)
}
// Get list
headers, err := c.ListMailbox(mailbox)
if err != nil {
return fatal("REST call failed", err)
}
for _, h := range headers {
fmt.Println(h.ID)
}
return subcommands.ExitSuccess
}

79
cmd/client/main.go Normal file
View File

@@ -0,0 +1,79 @@
// Package main implements a command line client for the Inbucket REST API
package main
import (
"context"
"flag"
"fmt"
"os"
"regexp"
"github.com/google/subcommands"
)
var host = flag.String("host", "localhost", "host/IP of Inbucket server")
var port = flag.Uint("port", 9000, "HTTP port of Inbucket server")
// Allow subcommands to accept regular expressions as flags
type regexFlag struct {
*regexp.Regexp
}
func (r *regexFlag) Defined() bool {
return r.Regexp != nil
}
func (r *regexFlag) Set(pattern string) error {
if pattern == "" {
r.Regexp = nil
return nil
}
re, err := regexp.Compile(pattern)
if err != nil {
return err
}
r.Regexp = re
return nil
}
func (r *regexFlag) String() string {
if r.Regexp == nil {
return ""
}
return r.Regexp.String()
}
// regexFlag must implement flag.Value
var _ flag.Value = &regexFlag{}
func main() {
// Important top-level flags
subcommands.ImportantFlag("host")
subcommands.ImportantFlag("port")
// Setup standard helpers
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
// Setup my commands
subcommands.Register(&listCmd{}, "")
subcommands.Register(&matchCmd{}, "")
subcommands.Register(&mboxCmd{}, "")
// Parse and execute
flag.Parse()
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}
func baseURL() string {
return fmt.Sprintf("http://%s:%v", *host, *port)
}
func fatal(msg string, err error) subcommands.ExitStatus {
fmt.Fprintf(os.Stderr, "%s: %v\n", msg, err)
return subcommands.ExitFailure
}
func usage(msg string) subcommands.ExitStatus {
fmt.Fprintln(os.Stderr, msg)
return subcommands.ExitUsageError
}

164
cmd/client/match.go Normal file
View File

@@ -0,0 +1,164 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"net/mail"
"os"
"time"
"github.com/google/subcommands"
"github.com/jhillyerd/inbucket/rest/client"
)
type matchCmd struct {
mailbox string
output string
outFunc func(headers []*client.MessageHeader) error
delete bool
// match criteria
from regexFlag
subject regexFlag
to regexFlag
maxAge time.Duration
}
func (*matchCmd) Name() string {
return "match"
}
func (*matchCmd) Synopsis() string {
return "output messages matching criteria"
}
func (*matchCmd) Usage() string {
return `match [flags] <mailbox>:
output messages matching all specified criteria
exit status will be 1 if no matches were found, otherwise 0
`
}
func (m *matchCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&m.output, "output", "id", "output format: id, json, or mbox")
f.BoolVar(&m.delete, "delete", false, "delete matched messages after output")
f.Var(&m.from, "from", "From header matching regexp (address, not name)")
f.Var(&m.subject, "subject", "Subject header matching regexp")
f.Var(&m.to, "to", "To header matching regexp (must match 1+ to address)")
f.DurationVar(
&m.maxAge, "maxage", 0,
"Matches must have been received in this time frame (ex: \"10s\", \"5m\")")
}
func (m *matchCmd) Execute(
_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
mailbox := f.Arg(0)
if mailbox == "" {
return usage("mailbox required")
}
// Select output function
switch m.output {
case "id":
m.outFunc = outputID
case "json":
m.outFunc = outputJSON
case "mbox":
m.outFunc = outputMbox
default:
return usage("unknown output type: " + m.output)
}
// Setup REST client
c, err := client.New(baseURL())
if err != nil {
return fatal("Couldn't build client", err)
}
// Get list
headers, err := c.ListMailbox(mailbox)
if err != nil {
return fatal("List REST call failed", err)
}
// Find matches
matches := make([]*client.MessageHeader, 0, len(headers))
for _, h := range headers {
if m.match(h) {
matches = append(matches, h)
}
}
// Return error status if no matches
if len(matches) == 0 {
return subcommands.ExitFailure
}
// Output matches
err = m.outFunc(matches)
if err != nil {
return fatal("Error", err)
}
if m.delete {
// Delete matches
for _, h := range matches {
err = h.Delete()
if err != nil {
return fatal("Delete REST call failed", err)
}
}
}
return subcommands.ExitSuccess
}
// match returns true if header matches all defined criteria
func (m *matchCmd) match(header *client.MessageHeader) bool {
if m.maxAge > 0 {
if time.Since(header.Date) > m.maxAge {
return false
}
}
if m.subject.Defined() {
if !m.subject.MatchString(header.Subject) {
return false
}
}
if m.from.Defined() {
from := header.From
addr, err := mail.ParseAddress(from)
if err == nil {
// Parsed successfully
from = addr.Address
}
if !m.from.MatchString(from) {
return false
}
}
if m.to.Defined() {
match := false
for _, to := range header.To {
addr, err := mail.ParseAddress(to)
if err == nil {
// Parsed successfully
to = addr.Address
}
if m.to.MatchString(to) {
match = true
break
}
}
if !match {
return false
}
}
return true
}
func outputID(headers []*client.MessageHeader) error {
for _, h := range headers {
fmt.Println(h.ID)
}
return nil
}
func outputJSON(headers []*client.MessageHeader) error {
jsonEncoder := json.NewEncoder(os.Stdout)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", " ")
return jsonEncoder.Encode(headers)
}

82
cmd/client/mbox.go Normal file
View File

@@ -0,0 +1,82 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"github.com/google/subcommands"
"github.com/jhillyerd/inbucket/rest/client"
)
type mboxCmd struct {
mailbox string
delete bool
}
func (*mboxCmd) Name() string {
return "mbox"
}
func (*mboxCmd) Synopsis() string {
return "output mailbox in mbox format"
}
func (*mboxCmd) Usage() string {
return `mbox [flags] <mailbox>:
output mailbox in mbox format
`
}
func (m *mboxCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&m.delete, "delete", false, "delete messages after output")
}
func (m *mboxCmd) Execute(
_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
mailbox := f.Arg(0)
if mailbox == "" {
return usage("mailbox required")
}
// Setup REST client
c, err := client.New(baseURL())
if err != nil {
return fatal("Couldn't build client", err)
}
// Get list
headers, err := c.ListMailbox(mailbox)
if err != nil {
return fatal("List REST call failed", err)
}
err = outputMbox(headers)
if err != nil {
return fatal("Error", err)
}
if m.delete {
// Delete matches
for _, h := range headers {
err = h.Delete()
if err != nil {
return fatal("Delete REST call failed", err)
}
}
}
return subcommands.ExitSuccess
}
// outputMbox renders messages in mbox format
// also used by match subcommand
func outputMbox(headers []*client.MessageHeader) error {
for _, h := range headers {
source, err := h.GetSource()
if err != nil {
return fmt.Errorf("Get source REST failed: %v", err)
}
fmt.Printf("From %s\n", h.From)
// TODO Escape "From " in message bodies with >
source.WriteTo(os.Stdout)
fmt.Println()
}
return nil
}

View File

@@ -15,11 +15,14 @@ apk add --no-cache --virtual .build-deps git
# Setup
export GOBIN="$bindir"
builddate="$(date -Iseconds)"
cd "$srcdir"
go clean
# Fetch tags for describe
git fetch -t
builddate="$(date -Iseconds)"
buildver="$(git describe --tags --always)"
# Build
go clean
echo "### Fetching Dependencies"
go get -t -v ./...
@@ -27,7 +30,7 @@ echo "### Testing Inbucket"
go test ./...
echo "### Building Inbucket"
go build -o inbucket -ldflags "-X 'main.BUILDDATE=$builddate'" -v .
go build -o inbucket -ldflags "-X 'main.version=$buildver' -X 'main.date=$builddate'" -v .
echo "### Installing Inbucket"
set -x

10
etc/travis-deploy.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
# travis-deploy.sh
# description: Trigger goreleaser deployment in correct build scenarios
set -eo pipefail
set -x
if [[ "$TRAVIS_GO_VERSION" == "$DEPLOY_WITH_MAJOR."* ]]; then
curl -sL https://git.io/goreleaser | bash
fi

View File

@@ -23,11 +23,11 @@ import (
)
var (
// VERSION contains the build version number, populated during linking by goxc
VERSION = "1.2.0-rc1"
// version contains the build version number, populated during linking
version = "1.2.0"
// BUILDDATE contains the build date, populated during linking by goxc
BUILDDATE = "undefined"
// date contains the build date, populated during linking
date = "undefined"
// Command line flags
help = flag.Bool("help", false, "Displays this help")
@@ -61,8 +61,8 @@ func init() {
}
func main() {
config.Version = VERSION
config.BuildDate = BUILDDATE
config.Version = version
config.BuildDate = date
flag.Parse()
if *help {

View File

@@ -63,13 +63,15 @@ func New(ctx context.Context, historyLen int) *Hub {
// history buffer and then relayed to all registered listeners.
func (hub *Hub) Dispatch(msg Message) {
hub.opChan <- func(h *Hub) {
// Add to history buffer
h.history.Value = msg
h.history = h.history.Next()
// Deliver message to all listeners, removing listeners if they return an error
for l := range h.listeners {
if err := l.Receive(msg); err != nil {
delete(h.listeners, l)
if h.history != nil {
// Add to history buffer
h.history.Value = msg
h.history = h.history.Next()
// Deliver message to all listeners, removing listeners if they return an error
for l := range h.listeners {
if err := l.Receive(msg); err != nil {
delete(h.listeners, l)
}
}
}
}

View File

@@ -60,6 +60,17 @@ func TestHubNew(t *testing.T) {
}
}
func TestHubZeroLen(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
hub := New(ctx, 0)
m := Message{}
for i := 0; i < 100; i++ {
hub.Dispatch(m)
}
// Just making sure Hub doesn't panic
}
func TestHubZeroListeners(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -1,3 +1,4 @@
// Package client provides a basic REST client for Inbucket
package client
import (
@@ -10,19 +11,19 @@ import (
"github.com/jhillyerd/inbucket/rest/model"
)
// ClientV1 accesses the Inbucket REST API v1
type ClientV1 struct {
// Client accesses the Inbucket REST API v1
type Client struct {
restClient
}
// NewV1 creates a new v1 REST API client given the base URL of an Inbucket server, ex:
// New creates a new v1 REST API client given the base URL of an Inbucket server, ex:
// "http://localhost:9000"
func NewV1(baseURL string) (*ClientV1, error) {
func New(baseURL string) (*Client, error) {
parsedURL, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
c := &ClientV1{
c := &Client{
restClient{
client: &http.Client{
Timeout: 30 * time.Second,
@@ -34,21 +35,31 @@ func NewV1(baseURL string) (*ClientV1, error) {
}
// ListMailbox returns a list of messages for the requested mailbox
func (c *ClientV1) ListMailbox(name string) (headers []*model.JSONMessageHeaderV1, err error) {
func (c *Client) ListMailbox(name string) (headers []*MessageHeader, err error) {
uri := "/api/v1/mailbox/" + url.QueryEscape(name)
err = c.doJSON("GET", uri, &headers)
if err != nil {
return nil, err
}
for _, h := range headers {
h.client = c
}
return
}
// GetMessage returns the message details given a mailbox name and message ID.
func (c *ClientV1) GetMessage(name, id string) (message *model.JSONMessageV1, err error) {
func (c *Client) GetMessage(name, id string) (message *Message, err error) {
uri := "/api/v1/mailbox/" + url.QueryEscape(name) + "/" + id
err = c.doJSON("GET", uri, &message)
if err != nil {
return nil, err
}
message.client = c
return
}
// GetMessageSource returns the message source given a mailbox name and message ID.
func (c *ClientV1) GetMessageSource(name, id string) (*bytes.Buffer, error) {
func (c *Client) GetMessageSource(name, id string) (*bytes.Buffer, error) {
uri := "/api/v1/mailbox/" + url.QueryEscape(name) + "/" + id + "/source"
resp, err := c.do("GET", uri)
if err != nil {
@@ -68,7 +79,7 @@ func (c *ClientV1) GetMessageSource(name, id string) (*bytes.Buffer, error) {
}
// DeleteMessage deletes a single message given the mailbox name and message ID.
func (c *ClientV1) DeleteMessage(name, id string) error {
func (c *Client) DeleteMessage(name, id string) error {
uri := "/api/v1/mailbox/" + url.QueryEscape(name) + "/" + id
resp, err := c.do("DELETE", uri)
if err != nil {
@@ -82,7 +93,7 @@ func (c *ClientV1) DeleteMessage(name, id string) error {
}
// PurgeMailbox deletes all messages in the given mailbox
func (c *ClientV1) PurgeMailbox(name string) error {
func (c *Client) PurgeMailbox(name string) error {
uri := "/api/v1/mailbox/" + url.QueryEscape(name)
resp, err := c.do("DELETE", uri)
if err != nil {
@@ -94,3 +105,40 @@ func (c *ClientV1) PurgeMailbox(name string) error {
}
return nil
}
// MessageHeader represents an Inbucket message sans content
type MessageHeader struct {
*model.JSONMessageHeaderV1
client *Client
}
// GetMessage returns this message with content
func (h *MessageHeader) GetMessage() (message *Message, err error) {
return h.client.GetMessage(h.Mailbox, h.ID)
}
// GetSource returns the source for this message
func (h *MessageHeader) GetSource() (*bytes.Buffer, error) {
return h.client.GetMessageSource(h.Mailbox, h.ID)
}
// Delete deletes this message from the mailbox
func (h *MessageHeader) Delete() error {
return h.client.DeleteMessage(h.Mailbox, h.ID)
}
// Message represents an Inbucket message including content
type Message struct {
*model.JSONMessageV1
client *Client
}
// GetSource returns the source for this message
func (m *Message) GetSource() (*bytes.Buffer, error) {
return m.client.GetMessageSource(m.Mailbox, m.ID)
}
// Delete deletes this message from the mailbox
func (m *Message) Delete() error {
return m.client.DeleteMessage(m.Mailbox, m.ID)
}

View File

@@ -5,7 +5,7 @@ import "testing"
func TestClientV1ListMailbox(t *testing.T) {
var want, got string
c, err := NewV1(baseURLStr)
c, err := New(baseURLStr)
if err != nil {
t.Fatal(err)
}
@@ -13,7 +13,7 @@ func TestClientV1ListMailbox(t *testing.T) {
c.client = mth
// Method under test
c.ListMailbox("testbox")
_, _ = c.ListMailbox("testbox")
want = "GET"
got = mth.req.Method
@@ -31,7 +31,7 @@ func TestClientV1ListMailbox(t *testing.T) {
func TestClientV1GetMessage(t *testing.T) {
var want, got string
c, err := NewV1(baseURLStr)
c, err := New(baseURLStr)
if err != nil {
t.Fatal(err)
}
@@ -39,7 +39,7 @@ func TestClientV1GetMessage(t *testing.T) {
c.client = mth
// Method under test
c.GetMessage("testbox", "20170107T224128-0000")
_, _ = c.GetMessage("testbox", "20170107T224128-0000")
want = "GET"
got = mth.req.Method
@@ -57,13 +57,12 @@ func TestClientV1GetMessage(t *testing.T) {
func TestClientV1GetMessageSource(t *testing.T) {
var want, got string
c, err := NewV1(baseURLStr)
c, err := New(baseURLStr)
if err != nil {
t.Fatal(err)
}
mth := &mockHTTPClient{
statusCode: 200,
body: "message source",
body: "message source",
}
c.client = mth
@@ -95,7 +94,7 @@ func TestClientV1GetMessageSource(t *testing.T) {
func TestClientV1DeleteMessage(t *testing.T) {
var want, got string
c, err := NewV1(baseURLStr)
c, err := New(baseURLStr)
if err != nil {
t.Fatal(err)
}
@@ -103,7 +102,10 @@ func TestClientV1DeleteMessage(t *testing.T) {
c.client = mth
// Method under test
c.DeleteMessage("testbox", "20170107T224128-0000")
err = c.DeleteMessage("testbox", "20170107T224128-0000")
if err != nil {
t.Fatal(err)
}
want = "DELETE"
got = mth.req.Method
@@ -121,7 +123,7 @@ func TestClientV1DeleteMessage(t *testing.T) {
func TestClientV1PurgeMailbox(t *testing.T) {
var want, got string
c, err := NewV1(baseURLStr)
c, err := New(baseURLStr)
if err != nil {
t.Fatal(err)
}
@@ -129,7 +131,10 @@ func TestClientV1PurgeMailbox(t *testing.T) {
c.client = mth
// Method under test
c.PurgeMailbox("testbox")
err = c.PurgeMailbox("testbox")
if err != nil {
t.Fatal(err)
}
want = "DELETE"
got = mth.req.Method
@@ -143,3 +148,176 @@ func TestClientV1PurgeMailbox(t *testing.T) {
t.Errorf("req.URL == %q, want %q", got, want)
}
}
func TestClientV1MessageHeader(t *testing.T) {
var want, got string
response := `[
{
"mailbox":"mailbox1",
"id":"id1",
"from":"from1",
"subject":"subject1",
"date":"2017-01-01T00:00:00.000-07:00",
"size":100
}
]`
c, err := New(baseURLStr)
if err != nil {
t.Fatal(err)
}
mth := &mockHTTPClient{body: response}
c.client = mth
// Method under test
headers, err := c.ListMailbox("testbox")
if err != nil {
t.Fatal(err)
}
want = "GET"
got = mth.req.Method
if got != want {
t.Errorf("req.Method == %q, want %q", got, want)
}
want = baseURLStr + "/api/v1/mailbox/testbox"
got = mth.req.URL.String()
if got != want {
t.Errorf("req.URL == %q, want %q", got, want)
}
if len(headers) != 1 {
t.Fatalf("len(headers) == %v, want 1", len(headers))
}
header := headers[0]
want = "mailbox1"
got = header.Mailbox
if got != want {
t.Errorf("Mailbox == %q, want %q", got, want)
}
want = "id1"
got = header.ID
if got != want {
t.Errorf("ID == %q, want %q", got, want)
}
want = "from1"
got = header.From
if got != want {
t.Errorf("From == %q, want %q", got, want)
}
want = "subject1"
got = header.Subject
if got != want {
t.Errorf("Subject == %q, want %q", got, want)
}
// Test MessageHeader.Delete()
mth.body = ""
err = header.Delete()
if err != nil {
t.Fatal(err)
}
want = "DELETE"
got = mth.req.Method
if got != want {
t.Errorf("req.Method == %q, want %q", got, want)
}
want = baseURLStr + "/api/v1/mailbox/mailbox1/id1"
got = mth.req.URL.String()
if got != want {
t.Errorf("req.URL == %q, want %q", got, want)
}
// Test MessageHeader.GetSource()
mth.body = "source1"
_, err = header.GetSource()
if err != nil {
t.Fatal(err)
}
want = "GET"
got = mth.req.Method
if got != want {
t.Errorf("req.Method == %q, want %q", got, want)
}
want = baseURLStr + "/api/v1/mailbox/mailbox1/id1/source"
got = mth.req.URL.String()
if got != want {
t.Errorf("req.URL == %q, want %q", got, want)
}
// Test MessageHeader.GetMessage()
mth.body = `{
"mailbox":"mailbox1",
"id":"id1",
"from":"from1",
"subject":"subject1",
"date":"2017-01-01T00:00:00.000-07:00",
"size":100
}`
message, err := header.GetMessage()
if err != nil {
t.Fatal(err)
}
if message == nil {
t.Fatalf("message was nil, wanted a value")
}
want = "GET"
got = mth.req.Method
if got != want {
t.Errorf("req.Method == %q, want %q", got, want)
}
want = baseURLStr + "/api/v1/mailbox/mailbox1/id1"
got = mth.req.URL.String()
if got != want {
t.Errorf("req.URL == %q, want %q", got, want)
}
// Test Message.Delete()
mth.body = ""
err = message.Delete()
if err != nil {
t.Fatal(err)
}
want = "DELETE"
got = mth.req.Method
if got != want {
t.Errorf("req.Method == %q, want %q", got, want)
}
want = baseURLStr + "/api/v1/mailbox/mailbox1/id1"
got = mth.req.URL.String()
if got != want {
t.Errorf("req.URL == %q, want %q", got, want)
}
// Test MessageHeader.GetSource()
mth.body = "source1"
_, err = message.GetSource()
if err != nil {
t.Fatal(err)
}
want = "GET"
got = mth.req.Method
if got != want {
t.Errorf("req.Method == %q, want %q", got, want)
}
want = baseURLStr + "/api/v1/mailbox/mailbox1/id1/source"
got = mth.req.URL.String()
if got != want {
t.Errorf("req.URL == %q, want %q", got, want)
}
}

View File

@@ -50,10 +50,9 @@ func (c *restClient) doJSON(method string, uri string, v interface{}) error {
if resp.StatusCode == http.StatusOK {
if v == nil {
return nil
} else {
// Decode response body
return json.NewDecoder(resp.Body).Decode(v)
}
// Decode response body
return json.NewDecoder(resp.Body).Decode(v)
}
return fmt.Errorf("Unexpected HTTP response status %v: %s", resp.StatusCode, resp.Status)

View File

@@ -28,6 +28,9 @@ type mockHTTPClient struct {
func (m *mockHTTPClient) Do(req *http.Request) (resp *http.Response, err error) {
m.req = req
if m.statusCode == 0 {
m.statusCode = 200
}
resp = &http.Response{
StatusCode: m.statusCode,
Body: ioutil.NopCloser(bytes.NewBufferString(m.body)),
@@ -64,13 +67,15 @@ func TestDoJSON(t *testing.T) {
var want, got string
mth := &mockHTTPClient{
statusCode: 200,
body: `{"foo": "bar"}`,
body: `{"foo": "bar"}`,
}
c := &restClient{mth, baseURL}
var v map[string]interface{}
c.doJSON("GET", "/doget", &v)
err := c.doJSON("GET", "/doget", &v)
if err != nil {
t.Fatal(err)
}
want = "GET"
got = mth.req.Method
@@ -98,7 +103,7 @@ func TestDoJSON(t *testing.T) {
func TestDoJSONNilV(t *testing.T) {
var want, got string
mth := &mockHTTPClient{statusCode: 200}
mth := &mockHTTPClient{}
c := &restClient{mth, baseURL}
err := c.doJSON("GET", "/doget", nil)

View File

@@ -181,9 +181,13 @@ func (mb *FileMailbox) GetMessage(id string) (Message, error) {
}
}
for _, m := range mb.messages {
if m.Fid == id {
return m, nil
if id == "latest" && len(mb.messages) != 0 {
return mb.messages[len(mb.messages)-1], nil
} else {
for _, m := range mb.messages {
if m.Fid == id {
return m, nil
}
}
}
@@ -254,22 +258,22 @@ func (mb *FileMailbox) writeIndex() error {
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil {
log.Errorf("Failed to close %q: %v", mb.indexPath, err)
}
}()
writer := bufio.NewWriter(file)
// Write each message and then flush
enc := gob.NewEncoder(writer)
for _, m := range mb.messages {
err = enc.Encode(m)
if err != nil {
_ = file.Close()
return err
}
}
if err := writer.Flush(); err != nil {
_ = file.Close()
return err
}
if err := file.Close(); err != nil {
log.Errorf("Failed to close %q: %v", mb.indexPath, err)
return err
}
} else {

View File

@@ -458,6 +458,55 @@ func TestFSNoMessageCap(t *testing.T) {
}
}
// Test Get the latest message
func TestGetLatestMessage(t *testing.T) {
ds, logbuf := setupDataStore(config.DataStoreConfig{})
defer teardownDataStore(ds)
// james hashes to 474ba67bdb289c6263b36dfd8a7bed6c85b04943
mbName := "james"
// Test empty mailbox
mb, err := ds.MailboxFor(mbName)
assert.Nil(t, err)
msg, err := mb.GetMessage("latest")
assert.Error(t, err)
fmt.Println(msg)
// Deliver test message
deliverMessage(ds, mbName, "test", time.Now())
// Deliver test message 2
id2, _ := deliverMessage(ds, mbName, "test 2", time.Now())
// Test get the latest message
mb, err = ds.MailboxFor(mbName)
assert.Nil(t, err)
msg, err = mb.GetMessage("latest")
assert.Nil(t, err)
assert.True(t, msg.ID() == id2, "Expected %q to be equal to %q", msg.ID(), id2)
// Deliver test message 3
id3, _ := deliverMessage(ds, mbName, "test 3", time.Now())
mb, err = ds.MailboxFor(mbName)
assert.Nil(t, err)
msg, err = mb.GetMessage("latest")
assert.Nil(t, err)
assert.True(t, msg.ID() == id3, "Expected %q to be equal to %q", msg.ID(), id3)
// Test wrong id
msg, err = mb.GetMessage("wrongid")
assert.Error(t, err)
if t.Failed() {
// Wait for handler to finish logging
time.Sleep(2 * time.Second)
// Dump buffered log data if there was a failure
_, _ = io.Copy(os.Stderr, logbuf)
}
}
// setupDataStore creates a new FileDataStore in a temporary directory
func setupDataStore(cfg config.DataStoreConfig) (*FileDataStore, *bytes.Buffer) {
path, err := ioutil.TempDir("", "inbucket")

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# run-tests.sh
# description: Generate test emails for Inbucket