feat: add more tests, partially document code

This commit is contained in:
Tomas Aparicio 2016-01-27 16:20:25 +00:00
parent 9cb131c037
commit b2ecdb85be
9 changed files with 178 additions and 65 deletions

12
.gitignore vendored
View File

@ -1,5 +1,4 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
imgine
*.o
*.a
*.so
@ -27,18 +26,11 @@ _testmain.go
*.test
bin/
.vagrant/
website/build/
website/npm-debug.log
*.old
*.attr
ui/.sass-cache
ui/static/base.css
ui/static/application.min.js
ui/dist/
*.swp
imaginary
bin/imaginary

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) Tomas Aparicio and contributors
Copyright (c) 2015, 2016 Tomas Aparicio and contributors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

66
health_test.go Normal file
View File

@ -0,0 +1,66 @@
package main
import "testing"
func TestToMegaBytes(t *testing.T) {
tests := []struct {
value uint64
expected float64
}{
{1024, 0},
{1024 * 1024, 1},
{1024 * 1024 * 10, 10},
{1024 * 1024 * 100, 100},
{1024 * 1024 * 250, 250},
}
for _, test := range tests {
val := toMegaBytes(test.value)
if val != test.expected {
t.Errorf("Invalid param: %#v != %#v", val, test.expected)
}
}
}
func TestRound(t *testing.T) {
tests := []struct {
value float64
expected int
}{
{0, 0},
{1, 1},
{1.56, 2},
{1.38, 1},
{30.12, 30},
}
for _, test := range tests {
val := round(test.value)
if val != test.expected {
t.Errorf("Invalid param: %#v != %#v", val, test.expected)
}
}
}
func TestToFixed(t *testing.T) {
tests := []struct {
value float64
expected float64
}{
{0, 0},
{1, 1},
{123, 123},
{0.99, 1},
{1.02, 1},
{1.82, 1.8},
{1.56, 1.6},
{1.38, 1.4},
}
for _, test := range tests {
val := toFixed(test.value, 1)
if val != test.expected {
t.Errorf("Invalid param: %#v != %#v", val, test.expected)
}
}
}

View File

@ -6,60 +6,21 @@ import (
"gopkg.in/h2non/bimg.v0"
)
type ImageOptions struct {
Width int
Height int
AreaWidth int
AreaHeight int
Quality int
Compression int
Rotate int
Top int
Left int
Margin int
Factor int
DPI int
TextWidth int
Force bool
NoCrop bool
NoReplicate bool
NoRotation bool
NoProfile bool
Opacity float32
Text string
Font string
Type string
Color []uint8
Gravity bimg.Gravity
Colorspace bimg.Interpretation
}
// Image stores an image binary buffer and its MIME type
type Image struct {
Body []byte
Mime string
}
// Operation implements an image transformation runnable interface
type Operation func([]byte, ImageOptions) (Image, error)
// Run performs the image transformation
func (o Operation) Run(buf []byte, opts ImageOptions) (Image, error) {
return o(buf, opts)
}
func BimgOptions(o ImageOptions) bimg.Options {
return bimg.Options{
Width: o.Width,
Height: o.Height,
Quality: o.Quality,
Compression: o.Compression,
NoAutoRotate: o.NoRotation,
NoProfile: o.NoProfile,
Force: o.Force,
Gravity: o.Gravity,
Interpretation: o.Colorspace,
Type: ImageType(o.Type),
}
}
// ImageInfo represents an image details and additional metadata
type ImageInfo struct {
Width int `json:"width"`
Height int `json:"height"`
@ -72,6 +33,8 @@ type ImageInfo struct {
}
func Info(buf []byte, o ImageOptions) (Image, error) {
// We're not handling an image here, but we reused the struct.
// An interface will be definitively better here.
image := Image{Mime: "application/json"}
meta, err := bimg.Metadata(buf)

22
image_test.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"io/ioutil"
"testing"
)
func TestImageResize(t *testing.T) {
opts := ImageOptions{Width: 300, Height: 300}
buf, _ := ioutil.ReadAll(readFile("imaginary.jpg"))
img, err := Resize(buf, opts)
if err != nil {
t.Errorf("Cannot process image: %s", err)
}
if img.Mime != "image/jpeg" {
t.Error("Invalid image MIME type")
}
if assertSize(img.Body, opts.Width, opts.Height) != nil {
t.Errorf("Invalid image size, expected: %dx%d", opts.Width, opts.Height)
}
}

6
log.go
View File

@ -21,23 +21,28 @@ type LogRecord struct {
elapsedTime time.Duration
}
// Log writes a log entry in the passed io.Writer stream
func (r *LogRecord) Log(out io.Writer) {
timeFormat := r.time.Format("02/Jan/2006 03:04:05")
request := fmt.Sprintf("%s %s %s", r.method, r.uri, r.protocol)
fmt.Fprintf(out, formatPattern, r.ip, timeFormat, request, r.status, r.responseBytes, r.elapsedTime.Seconds())
}
// Write acts like a proxy passing the given bytes buffer to the ResponseWritter
// and additionally counting the passed amount of bytes for logging usage.
func (r *LogRecord) Write(p []byte) (int, error) {
written, err := r.ResponseWriter.Write(p)
r.responseBytes += int64(written)
return written, err
}
// WriteHeader
func (r *LogRecord) WriteHeader(status int) {
r.status = status
r.ResponseWriter.WriteHeader(status)
}
// LogHandler maps the HTTP handler with a custom io.Writer compatible stream
type LogHandler struct {
handler http.Handler
io io.Writer
@ -48,6 +53,7 @@ func NewLog(handler http.Handler, io io.Writer) http.Handler {
return &LogHandler{handler, io}
}
// Implementes the required method as standard HTTP handler, serving the request.
func (h *LogHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
clientIP := r.RemoteAddr
if colon := strings.LastIndex(clientIP, ":"); colon != -1 {

View File

@ -79,7 +79,7 @@ func validate(next http.Handler) http.Handler {
func validateImage(next http.Handler, o ServerOptions) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if r.Method == "GET" && isPrivatePath(path) {
if r.Method == "GET" && isPublicPath(path) {
next.ServeHTTP(w, r)
return
}
@ -120,24 +120,25 @@ func setCacheHeaders(next http.Handler, ttl int) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer next.ServeHTTP(w, r)
if r.Method != "GET" || isPrivatePath(r.URL.Path) {
if r.Method != "GET" || isPublicPath(r.URL.Path) {
return
}
var cacheControl string
if ttl == 0 {
cacheControl = "private, no-cache, no-store, must-revalidate"
} else {
cacheControl = fmt.Sprintf("public, s-maxage: %d, max-age: %d, no-transform", ttl, ttl)
}
ttlDiff := time.Duration(ttl) * time.Second
expires := time.Now().Add(ttlDiff)
w.Header().Add("Expires", expires.Format(time.RFC1123))
w.Header().Add("Cache-Control", cacheControl)
w.Header().Add("Cache-Control", getCacheControl(ttl))
})
}
func isPrivatePath(path string) bool {
func getCacheControl(ttl int) string {
if ttl == 0 {
return "private, no-cache, no-store, must-revalidate"
}
return fmt.Sprintf("public, s-maxage: %d, max-age: %d, no-transform", ttl, ttl)
}
func isPublicPath(path string) bool {
return path == "/" || path == "/health" || path == "/form"
}

48
options.go Normal file
View File

@ -0,0 +1,48 @@
package main
import "gopkg.in/h2non/bimg.v0"
// ImageOptions represent all the supported image transformation params as first level members
type ImageOptions struct {
Width int
Height int
AreaWidth int
AreaHeight int
Quality int
Compression int
Rotate int
Top int
Left int
Margin int
Factor int
DPI int
TextWidth int
Force bool
NoCrop bool
NoReplicate bool
NoRotation bool
NoProfile bool
Opacity float32
Text string
Font string
Type string
Color []uint8
Gravity bimg.Gravity
Colorspace bimg.Interpretation
}
// BimgOptions creates a new bimg compatible options struct mapping the fields properly
func BimgOptions(o ImageOptions) bimg.Options {
return bimg.Options{
Width: o.Width,
Height: o.Height,
Quality: o.Quality,
Compression: o.Compression,
NoAutoRotate: o.NoRotation,
NoProfile: o.NoProfile,
Force: o.Force,
Gravity: o.Gravity,
Interpretation: o.Colorspace,
Type: ImageType(o.Type),
}
}

15
options_test.go Normal file
View File

@ -0,0 +1,15 @@
package main
import "testing"
func TestBimgOptions(t *testing.T) {
imgOpts := ImageOptions{
Width: 500,
Height: 600,
}
opts := BimgOptions(imgOpts)
if opts.Width != imgOpts.Width || opts.Height != imgOpts.Height {
t.Error("Invalid width and height")
}
}