├── LICENSE ├── README.md ├── build.sh ├── config └── config.go ├── index.js ├── lib ├── nodewrapper │ ├── nodewrapper.go │ └── nodewrapper_test.go └── osutil │ ├── osutil.go │ └── osutil_test.go ├── main.go └── main_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Patrick Kosterman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AWS Lambda Barcode-generator 2 | =========== 3 | 4 | This is a sample project to demonstrate usage of Golang for AWS Lambda. 5 | 6 | AWS Lambda is a cloud computing service that lets you run code without provisioning or managing servers. AWS Lambda executes your code only when needed and scales automatically. 7 | 8 | Currently AWS Lambda natively supports Java, Node.js, Python, and C#. 9 | 10 | This project uses a Node.js wrapper to build a Go Lambda function that generates and returns a barcode when triggered by AWS API Gateway. The barcode is returned as a base64 encoded PNG image string. 11 | 12 | The Node.js wrapper keeps a Go process around to handle multiple invocations. The first time the function runs it will take a bit longer, but after that it greatly increases performance. 13 | 14 | ## Why Go? 15 | 16 | I have been using Go for the past 3 years, it's our language of choice at PassKit because of: 17 | 18 | * Speed! Very fast & a perfect choice for CPU-intensive tasks. 19 | * Quick & easy to master in a very short amount of time. 20 | * Portability across platforms. 21 | * Compiled binariers: plays nice with Docker. 22 | * Excellent concurreny primitives. 23 | * Well defined error handling patterns. 24 | * Rich standard libraries. 25 | * Standard code formatting / ease of maintenance. 26 | 27 | ## Inspiration 28 | The Node.js wrapper used in this project is inspired by: 29 | * lambda_proc 30 | * Amazon AWS Lambda & Tcl 31 | 32 | ## Full Demo & Instructions 33 | 34 | Click here for a detailed article on how to set this up with AWS API Gateway and Route 53. 35 | 36 | ## Build 37 | Clone this repo and cd into the project root then: 38 | 39 | ```bash 40 | ./build.sh 41 | ``` 42 | 43 | This will place a lambda.zip file into the build folder. You can update this zip file 44 | into AWS Lambda. 45 | 46 | If you want to run `go test`, then you will also need to install lambda-test (Node command-line tool). I will 47 | place a repo of this on github shortly - just need to rewrite some of the logic. -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # clean the build directory 6 | echo "Cleaning build folder" 7 | test -d "./build" && rm -rf build 8 | mkdir ./build 9 | 10 | echo "Setting Production Mode" 11 | mv config/config.go config/config.tmp 12 | sed -e "/const Develop = /s/.*/const Develop = false/g" config/config.tmp > config/config.go 13 | 14 | echo "Compiling for Linux" 15 | GOOS=linux go build -o ./build/main 16 | cp index.js build 17 | 18 | echo "Preparing Lambda bundle" 19 | cd build 20 | sed -e "/const exeName = /s/.*/const exeName = 'main';/g" index.js > index.tmp 21 | sed -e "s/done(output, null);/done(output.errorMessage, null);/g" index.tmp > index.js 22 | zip -r lambda.zip main index.js 23 | 24 | echo "Cleaning up" 25 | rm index.* main 26 | mv ../config/config.tmp ../config/config.go 27 | echo "Done!" 28 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // NOTE Build Script will change develop flag to false 4 | const DEVELOP = true 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Will pass a JSON string via stdin to the go function and will catch and relay 2 | // back the JSON response via stdout. The go executable process will be reused 3 | // and restarted upon faileure. This should provide lower latency after the 4 | // first call. 5 | 6 | // set to name of the package for local testing - buildscript will rename to 'main' 7 | const exeName = 'aws-lambda-barcode-generator'; 8 | 9 | // It shouldn't be necessary to modify below this line 10 | const MAX_FAILS = 4; 11 | 12 | var child_process = require('child_process'), 13 | go_proc = null, 14 | done = console.log.bind(console), 15 | fails = 0; 16 | 17 | (function new_go_proc() { 18 | 19 | // pipe stdin/out, blind passthru stderr 20 | go_proc = child_process.spawn('./' + exeName, { stdio: ['pipe', 'pipe', process.stderr] }); 21 | 22 | go_proc.on('error', function(err) { 23 | process.stderr.write("go_proc errored: "+JSON.stringify(err)+"\n"); 24 | if (++fails > MAX_FAILS) { 25 | process.exit(1); // force container restart after too many fails 26 | } 27 | new_go_proc(); 28 | done(err); 29 | }); 30 | 31 | go_proc.on('exit', function(code) { 32 | process.stderr.write("go_proc exited prematurely with code: "+code+"\n"); 33 | if (++fails > MAX_FAILS) { 34 | process.exit(1); // force container restart after too many fails 35 | } 36 | new_go_proc(); 37 | done(new Error("Exited with code "+code)); 38 | }); 39 | 40 | go_proc.stdin.on('error', function(err) { 41 | process.stderr.write("go_proc stdin write error: "+JSON.stringify(err)+"\n"); 42 | if (++fails > MAX_FAILS) { 43 | process.exit(1); // force container restart after too many fails 44 | } 45 | new_go_proc(); 46 | done(err); 47 | }); 48 | 49 | var data = null; 50 | go_proc.stdout.on('data', function(chunk) { 51 | fails = 0; // reset fails 52 | if (data === null) { 53 | data = new Buffer(chunk); 54 | } else { 55 | data.write(chunk); 56 | } 57 | // check for newline ascii char 10 58 | if (data.length && data[data.length-1] == 10) { 59 | var output = JSON.parse(data.toString('UTF-8')); 60 | data = null; 61 | if (output.errorMessage) { 62 | // line will be replaced with 'done(output.errorMessage, null)' by build script 63 | done(output, null); 64 | } else { 65 | done(null, output); 66 | } 67 | }; 68 | }); 69 | })(); 70 | 71 | exports.handler = function(event, context) { 72 | 73 | // always output to current context's done 74 | done = context.done.bind(context); 75 | 76 | go_proc.stdin.write(JSON.stringify({ 77 | "event": event, 78 | "context": context 79 | })+"\n"); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /lib/nodewrapper/nodewrapper.go: -------------------------------------------------------------------------------- 1 | package nodewrapper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type ( 13 | Handler func(*Context, json.RawMessage) (interface{}, error) 14 | 15 | Context struct { 16 | AwsRequestID string `json:"awsRequestId"` 17 | FunctionName string `json:"functionName"` 18 | FunctionVersion string `json:"functionVersion"` 19 | Invokeid string `json:"invokeid"` 20 | IsDefaultFunctionVersion bool `json:"isDefaultFunctionVersion"` 21 | LogGroupName string `json:"logGroupName"` 22 | LogStreamName string `json:"logStreamName"` 23 | MemoryLimitInMB string `json:"memoryLimitInMB"` 24 | } 25 | 26 | Payload struct { 27 | // custom event fields 28 | Event json.RawMessage `json:"event"` 29 | 30 | // default context object 31 | Context *Context `json:"context"` 32 | } 33 | 34 | Response struct { 35 | // Request id is an incremental integer 36 | // representing the request that has been 37 | // received by this go proc during it's 38 | // lifetime 39 | Success *bool `json:"success,omitempty"` 40 | RequestId int `json:"-"` // can retrun request_id for debugging"` 41 | // Any errors that occur during processing 42 | // or are returned by handlers are returned 43 | Error *string `json:"errorMessage,omitempty"` 44 | Token *string `json:"token,omitempty"` 45 | // Response output data returned if no errors are present 46 | Data *interface{} `json:"response,omitempty"` 47 | } 48 | ) 49 | 50 | var requestId int // process req id 51 | 52 | func NewErrorResponse(err error) *Response { 53 | e := err.Error() 54 | errorEscaped := strings.Replace(strings.Replace(e, "\n", "", -1), "\t", " ", -1) 55 | return &Response{ 56 | RequestId: requestId, 57 | Error: &errorEscaped, 58 | } 59 | } 60 | 61 | func NewResponse(data interface{}) *Response { 62 | success := true 63 | 64 | if token, ok := data.(map[string]interface{})["token"]; ok { 65 | 66 | delete(data.(map[string]interface{}), "token") 67 | 68 | return &Response{ 69 | Success: &success, 70 | Token: token.(*string), 71 | RequestId: requestId, 72 | Data: &data, 73 | } 74 | } 75 | 76 | return &Response{ 77 | Success: &success, 78 | RequestId: requestId, 79 | Data: &data, 80 | } 81 | } 82 | 83 | func Run(handler Handler, rawOutput bool) { 84 | RunStream(handler, os.Stdin, os.Stdout, rawOutput) 85 | } 86 | 87 | func RunStream(handler Handler, Stdin io.Reader, Stdout io.Writer, rawOutput bool) { 88 | 89 | stdin := json.NewDecoder(Stdin) 90 | stdout := json.NewEncoder(Stdout) 91 | decodeError := false 92 | 93 | for ; ; requestId++ { 94 | if err := func() (err error) { 95 | defer func() { 96 | if e := recover(); e != nil { 97 | err = fmt.Errorf("Server Error: 'Panic' %v", e) 98 | } 99 | }() 100 | var payload Payload 101 | if err := stdin.Decode(&payload); err != nil { 102 | decodeError = true 103 | return fmt.Errorf("Bad Request: %v", err) 104 | } 105 | data, err := handler(payload.Context, payload.Event) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | if rawOutput { 111 | // for this particular purpose return as bytestream 112 | return stdout.Encode(data) 113 | } 114 | return stdout.Encode(NewResponse(data)) 115 | }(); err != nil { 116 | if encErr := stdout.Encode(NewErrorResponse(err)); encErr != nil { 117 | // bad times 118 | requestId++ 119 | break 120 | log.Println("Failed to encode err response!", encErr.Error()) 121 | } else { 122 | // if invalid JSON, break to advance stdin and restart the loop 123 | if decodeError { 124 | requestId++ 125 | break 126 | } 127 | } 128 | } 129 | } 130 | 131 | RunStream(handler, Stdin, Stdout, rawOutput) 132 | } 133 | -------------------------------------------------------------------------------- /lib/nodewrapper/nodewrapper_test.go: -------------------------------------------------------------------------------- 1 | package nodewrapper 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestRunStream(t *testing.T) { 11 | 12 | const TestRecords = 100 13 | 14 | type Record struct { 15 | Id int `json:"id"` 16 | } 17 | 18 | ctx := &Context{ 19 | AwsRequestID: "awsRequestId", 20 | FunctionName: "functionName", 21 | FunctionVersion: "functionVersion", 22 | Invokeid: "invokeid", 23 | IsDefaultFunctionVersion: true, 24 | LogGroupName: "logGroupName", 25 | LogStreamName: "logStreamName", 26 | MemoryLimitInMB: "memoryLimitInMB", 27 | } 28 | 29 | records := &bytes.Buffer{} 30 | enc := json.NewEncoder(records) 31 | for i := 0; i < TestRecords; i++ { 32 | data, err := json.Marshal(&Record{Id: i}) 33 | if err != nil { 34 | t.Error(err) 35 | } 36 | if err := enc.Encode(&Payload{Context: ctx, Event: json.RawMessage(data)}); err != nil { 37 | t.Error(err) 38 | } 39 | } 40 | 41 | r, w := io.Pipe() 42 | 43 | go func() { 44 | RunStream(func(c *Context, data json.RawMessage) (interface{}, error) { 45 | if c.AwsRequestID != ctx.AwsRequestID { 46 | t.Errorf("Expected %v, got %v", ctx.AwsRequestID, c.AwsRequestID) 47 | } 48 | if c.FunctionName != ctx.FunctionName { 49 | t.Errorf("Expected %v, got %v", ctx.FunctionName, c.FunctionName) 50 | } 51 | if c.FunctionVersion != ctx.FunctionVersion { 52 | t.Errorf("Expected %v, got %v", ctx.FunctionVersion, c.FunctionVersion) 53 | } 54 | if c.Invokeid != ctx.Invokeid { 55 | t.Errorf("Expected %v, got %v", ctx.Invokeid, c.Invokeid) 56 | } 57 | if c.IsDefaultFunctionVersion != ctx.IsDefaultFunctionVersion { 58 | t.Errorf("Expected %v, got %v", ctx.IsDefaultFunctionVersion, c.IsDefaultFunctionVersion) 59 | } 60 | if c.LogGroupName != ctx.LogGroupName { 61 | t.Errorf("Expected %v, got %v", ctx.LogGroupName, c.LogGroupName) 62 | } 63 | if c.LogStreamName != ctx.LogStreamName { 64 | t.Errorf("Expected %v, got %v", ctx.LogStreamName, c.LogStreamName) 65 | } 66 | if c.MemoryLimitInMB != ctx.MemoryLimitInMB { 67 | t.Errorf("Expected %v, got %v", ctx.MemoryLimitInMB, c.MemoryLimitInMB) 68 | } 69 | var rec Record 70 | if err := json.Unmarshal(data, &rec); err != nil { 71 | t.Error(err) 72 | } 73 | return &rec, nil 74 | }, records, w) 75 | }() 76 | 77 | dec := json.NewDecoder(r) 78 | for i := 0; i < TestRecords; i++ { 79 | var resp Response 80 | if err := dec.Decode(&resp); err != nil { 81 | t.Error(err) 82 | } 83 | if resp.Error != nil { 84 | t.Errorf("Expected nil error, got: %v", resp.Error) 85 | } 86 | 87 | // Removed for production 88 | /*if resp.RequestId != i { 89 | t.Errorf("Expected %d, got %d", i, resp.RequestId) 90 | }*/ 91 | 92 | testData := *resp.Data 93 | data, ok := testData.(map[string]interface{}) 94 | if !ok { 95 | t.Errorf("Expected type map[string]interface{}, got %T", testData) 96 | } 97 | if data["id"].(float64) != float64(i) { 98 | t.Errorf("Expected %d, got %v", i, data["id"]) 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lib/osutil/osutil.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | // Helper function to make simplify command line access 10 | func Run(dir string, commandLine string) ([]byte, error) { 11 | 12 | // Split commandLine into an array separated by whitespace 13 | args := strings.Fields(commandLine) 14 | cmd := exec.Command(args[0], args[1:]...) 15 | cmd.Dir = dir 16 | var buf bytes.Buffer 17 | cmd.Stdout = &buf 18 | cmd.Stderr = &buf 19 | err := cmd.Run() 20 | out := buf.Bytes() 21 | if err != nil { 22 | return out, err 23 | } 24 | return out, nil 25 | } 26 | -------------------------------------------------------------------------------- /lib/osutil/osutil_test.go: -------------------------------------------------------------------------------- 1 | package osutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRunExec(t *testing.T) { 10 | 11 | assert := assert.New(t) 12 | 13 | ls, err := Run("./", "ls -la") 14 | assert.Nil(err, "Run returned an unexpected error") 15 | if assert.NotNil(ls, "Run ls -la failed to return a result") { 16 | assert.Contains(string(ls), "osutil.go", "Run 'ls -la' failed to contain file in chosen folder") 17 | assert.Contains(string(ls), "osutil_test.go", "Run 'ls -la' failed to contain second file in chosen folder") 18 | } 19 | 20 | // Test with many paramaters 21 | ls, err = Run("../../../", `stat -f %N-%A main.go`) 22 | assert.Nil(err, "Run returned an unexpected error") 23 | if assert.NotNil(ls, "Run ls -la failed to return a result") { 24 | assert.Contains(string(ls), "main.go-644", "Run 'stat -f' failed to contain file in chosen folder") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "fmt" 8 | "image/png" 9 | "os" 10 | 11 | "aws-lambda-barcode-generator/config" 12 | "aws-lambda-barcode-generator/lib/nodewrapper" 13 | 14 | "github.com/boombuler/barcode" 15 | "github.com/boombuler/barcode/pdf417" 16 | "github.com/boombuler/barcode/qr" 17 | ) 18 | 19 | type LambdaInput struct { 20 | Width *int `json:"width,omitempty"` 21 | Height *int `json:"height,omitempty"` 22 | Message *string `json:"message,omitempty"` 23 | Type *string `json:"type,omitempty"` 24 | } 25 | 26 | func main() { 27 | nodewrapper.Run(func(context *nodewrapper.Context, eventJSON json.RawMessage) (interface{}, error) { 28 | 29 | var input LambdaInput 30 | var c barcode.Barcode 31 | var err error 32 | 33 | if err = json.Unmarshal(eventJSON, &input); err != nil { 34 | // Add error prefixes to all final error strings to allow for AWS API Gateway regex 35 | return nil, fmt.Errorf("400 Bad Request: Invalid Request - cannot marshal JSON input. Object received: %s", string(eventJSON)) 36 | } 37 | 38 | err = checkValidRequest(input) 39 | 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | switch barcodeType := *input.Type; barcodeType { 45 | case "qr": 46 | c, err = qr.Encode(*input.Message, qr.M, qr.Unicode) 47 | case "pdf417": 48 | c, err = pdf417.Encode(*input.Message, 3) 49 | } 50 | 51 | if err != nil { 52 | return nil, fmt.Errorf("500 Server Error: Could not generate barcode. Details: %s", err) 53 | } 54 | 55 | // Scale the barcode to 200x200 pixels 56 | c, err = barcode.Scale(c, *input.Width, *input.Height) 57 | 58 | if err != nil { 59 | return nil, fmt.Errorf("500 Server Error: Could not scale the barcode to input width and height. Details: %s", err) 60 | } 61 | 62 | // if we have develop set to true, then also export as PNG (so can view on local machine) 63 | if config.DEVELOP { 64 | // create the output file 65 | file, _ := os.Create("code.png") 66 | defer file.Close() 67 | 68 | // encode the barcode as png 69 | png.Encode(file, c) 70 | } 71 | 72 | // write image to buffer 73 | buf := new(bytes.Buffer) 74 | err = png.Encode(buf, c) 75 | 76 | if err != nil { 77 | return nil, fmt.Errorf("500 Server Error: Could not write image to buffer. Details: %s", err) 78 | } 79 | 80 | // return the image as a base64 encoded string 81 | return base64.StdEncoding.EncodeToString(buf.Bytes()), err 82 | }, true) 83 | } 84 | 85 | func checkValidRequest(input LambdaInput) error { 86 | // check if input conforms with our spec 87 | if input.Height == nil { 88 | return fmt.Errorf("400 Bad Request: 'height' field is missing.") 89 | } 90 | 91 | if input.Width == nil { 92 | return fmt.Errorf("400 Bad Request: 'width' field is missing.") 93 | } 94 | 95 | if input.Message == nil { 96 | return fmt.Errorf("400 Bad Request: 'message' field is missing.") 97 | } 98 | 99 | if input.Type == nil { 100 | return fmt.Errorf("400 Bad Request: 'type' field is missing.") 101 | } 102 | 103 | h := *input.Height 104 | w := *input.Width 105 | m := *input.Message 106 | t := *input.Type 107 | 108 | if h < 150 { 109 | return fmt.Errorf("400 Bad Request: 'height' needs to be a minimum of 150.") 110 | } 111 | 112 | if h > 3000 { 113 | return fmt.Errorf("400 Bad Request: 'height' can be a maximum of 3000.") 114 | } 115 | 116 | if w < 150 { 117 | return fmt.Errorf("400 Bad Request: 'width' needs to be a minimum of 150.") 118 | } 119 | 120 | if w > 3000 { 121 | return fmt.Errorf("400 Bad Request: 'width' can be a maximum of 3000.") 122 | } 123 | 124 | if len(m) > 600 { 125 | return fmt.Errorf("400 Bad Request: Your message is too large, it can be a maximum of 600 bytes.") 126 | } 127 | 128 | if t != "qr" && t != "pdf417" { 129 | return fmt.Errorf("400 Bad Request: Invalid barcode type. Type needs to be: 'qr', 'pdf417'.") 130 | } 131 | 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Testing requires the node package lambda-test 2 | // Download from github link: 3 | 4 | package main 5 | 6 | import ( 7 | "aws-lambda-barcode-generator/lib/osutil" 8 | "encoding/json" 9 | "fmt" 10 | 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | type lambdaInputTest struct { 17 | Width interface{} `json:"width,omitempty"` 18 | Height interface{} `json:"height,omitempty"` 19 | Message *string `json:"message,omitempty"` 20 | Type *string `json:"type,omitempty"` 21 | } 22 | 23 | type lambdaOutput struct { 24 | Response *interface{} `json:"response,omitempty"` 25 | Error *string `json:"errorMessage,omitempty"` 26 | } 27 | 28 | // Tests will be called on the compiled binary so ensure we are running the latest version 29 | func TestBuild(t *testing.T) { 30 | assert := assert.New(t) 31 | 32 | _, err := osutil.Run("", "go build") 33 | assert.Nil(err, fmt.Sprintf("Build failed: %v", err)) 34 | } 35 | 36 | func TestMissingHeight(t *testing.T) { 37 | width := 200 38 | message := "Test" 39 | ctype := "qr" 40 | 41 | l := lambdaInputTest{ 42 | Width: &width, 43 | Message: &message, 44 | Type: &ctype, 45 | } 46 | 47 | resp, err := runLambda(l, 10) 48 | 49 | assert := assert.New(t) 50 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 51 | assert.Equal(*resp.Error, "400 Bad Request: 'height' field is missing.") 52 | } 53 | 54 | func TestMissingWidth(t *testing.T) { 55 | height := 200 56 | message := "Test" 57 | ctype := "qr" 58 | 59 | l := lambdaInputTest{ 60 | Height: &height, 61 | Message: &message, 62 | Type: &ctype, 63 | } 64 | 65 | resp, err := runLambda(l, 10) 66 | 67 | assert := assert.New(t) 68 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 69 | assert.Equal(*resp.Error, "400 Bad Request: 'width' field is missing.") 70 | } 71 | 72 | func TestMissingMessage(t *testing.T) { 73 | height := 200 74 | width := 200 75 | ctype := "qr" 76 | 77 | l := lambdaInputTest{ 78 | Height: &height, 79 | Width: &width, 80 | Type: &ctype, 81 | } 82 | 83 | resp, err := runLambda(l, 10) 84 | 85 | assert := assert.New(t) 86 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 87 | assert.Equal(*resp.Error, "400 Bad Request: 'message' field is missing.") 88 | } 89 | 90 | func TestMissingType(t *testing.T) { 91 | height := 200 92 | width := 200 93 | message := "Test" 94 | 95 | l := lambdaInputTest{ 96 | Height: &height, 97 | Width: &width, 98 | Message: &message, 99 | } 100 | 101 | resp, err := runLambda(l, 10) 102 | 103 | assert := assert.New(t) 104 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 105 | assert.Equal(*resp.Error, "400 Bad Request: 'type' field is missing.") 106 | } 107 | 108 | func TestInvalidType(t *testing.T) { 109 | height := 200 110 | width := 200 111 | message := "Test" 112 | ctype := "test" 113 | 114 | l := lambdaInputTest{ 115 | Height: &height, 116 | Width: &width, 117 | Type: &ctype, 118 | Message: &message, 119 | } 120 | 121 | resp, err := runLambda(l, 10) 122 | 123 | assert := assert.New(t) 124 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 125 | assert.Equal(*resp.Error, "400 Bad Request: Invalid barcode type. Type needs to be: 'qr', 'pdf417'.") 126 | } 127 | 128 | func TestInvalidHeight(t *testing.T) { 129 | height := "testnotallowed" 130 | width := 200 131 | message := "Test" 132 | ctype := "qr" 133 | 134 | l := lambdaInputTest{ 135 | Height: &height, 136 | Width: &width, 137 | Message: &message, 138 | Type: &ctype, 139 | } 140 | 141 | resp, err := runLambda(l, 10) 142 | 143 | assert := assert.New(t) 144 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 145 | assert.Contains(*resp.Error, "400 Bad Request: Invalid Request - cannot marshal JSON input.") 146 | } 147 | 148 | func TestInvalidWidth(t *testing.T) { 149 | height := 200 150 | width := "testnotallowed" 151 | message := "Test" 152 | ctype := "qr" 153 | 154 | l := lambdaInputTest{ 155 | Height: &height, 156 | Width: &width, 157 | Message: &message, 158 | Type: &ctype, 159 | } 160 | 161 | resp, err := runLambda(l, 10) 162 | 163 | assert := assert.New(t) 164 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 165 | assert.Contains(*resp.Error, "400 Bad Request: Invalid Request - cannot marshal JSON input.") 166 | } 167 | 168 | func TestMinimumHeight(t *testing.T) { 169 | height := 149 170 | width := 200 171 | message := "Test" 172 | ctype := "qr" 173 | 174 | l := lambdaInputTest{ 175 | Height: &height, 176 | Width: &width, 177 | Message: &message, 178 | Type: &ctype, 179 | } 180 | 181 | resp, err := runLambda(l, 10) 182 | 183 | assert := assert.New(t) 184 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 185 | assert.Equal(*resp.Error, "400 Bad Request: 'height' needs to be a minimum of 150.") 186 | } 187 | 188 | func TestMaximumHeight(t *testing.T) { 189 | height := 3001 190 | width := 200 191 | message := "Test" 192 | ctype := "qr" 193 | 194 | l := lambdaInputTest{ 195 | Height: &height, 196 | Width: &width, 197 | Message: &message, 198 | Type: &ctype, 199 | } 200 | 201 | resp, err := runLambda(l, 10) 202 | 203 | assert := assert.New(t) 204 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 205 | assert.Equal(*resp.Error, "400 Bad Request: 'height' can be a maximum of 3000.") 206 | } 207 | 208 | func TestMinimumWidth(t *testing.T) { 209 | height := 200 210 | width := 149 211 | message := "Test" 212 | ctype := "qr" 213 | 214 | l := lambdaInputTest{ 215 | Height: &height, 216 | Width: &width, 217 | Message: &message, 218 | Type: &ctype, 219 | } 220 | 221 | resp, err := runLambda(l, 10) 222 | 223 | assert := assert.New(t) 224 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 225 | assert.Equal(*resp.Error, "400 Bad Request: 'width' needs to be a minimum of 150.") 226 | } 227 | 228 | func TestMaximumWidth(t *testing.T) { 229 | height := 200 230 | width := 3001 231 | message := "Test" 232 | ctype := "qr" 233 | 234 | l := lambdaInputTest{ 235 | Height: &height, 236 | Width: &width, 237 | Message: &message, 238 | Type: &ctype, 239 | } 240 | 241 | resp, err := runLambda(l, 10) 242 | 243 | assert := assert.New(t) 244 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 245 | assert.Equal(*resp.Error, "400 Bad Request: 'width' can be a maximum of 3000.") 246 | } 247 | 248 | func TestMaximalMessageLength(t *testing.T) { 249 | height := 200 250 | width := 200 251 | message := "" 252 | ctype := "qr" 253 | 254 | for i := 0; i <= 100; i++ { 255 | message += "abcdefgh" 256 | } 257 | 258 | l := lambdaInputTest{ 259 | Height: &height, 260 | Width: &width, 261 | Message: &message, 262 | Type: &ctype, 263 | } 264 | 265 | resp, err := runLambda(l, 10) 266 | 267 | assert := assert.New(t) 268 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 269 | assert.Equal(*resp.Error, "400 Bad Request: Your message is too large, it can be a maximum of 600 bytes.") 270 | } 271 | 272 | func TestSuccesfulBarcode(t *testing.T) { 273 | height := 200 274 | width := 600 275 | message := "測試這個" 276 | ctype := "qr" 277 | 278 | l := lambdaInputTest{ 279 | Height: &height, 280 | Width: &width, 281 | Message: &message, 282 | Type: &ctype, 283 | } 284 | 285 | runLambda(l, 10) 286 | 287 | /*resp, err := runLambda(l, 10) 288 | 289 | assert := assert.New(t) 290 | assert.Nil(err, fmt.Sprintf("lambda-test returned an unexpected error: %v", err)) 291 | assert.Equal(*resp.Error, "400 Bad Request: Your message is too large, it can be a maximum of 600 bytes.")*/ 292 | 293 | } 294 | 295 | // Helper method, runs lambda-test and passes in the JSON 296 | func runLambda(request lambdaInputTest, timeout int) (*lambdaOutput, error) { 297 | requestJSON, err := json.Marshal(request) 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | response, err := osutil.Run("", fmt.Sprintf("lambda-test -e %v -t %v", string(requestJSON), timeout)) 303 | responseObject := &lambdaOutput{} 304 | 305 | if err != nil { 306 | return nil, err 307 | } else { 308 | json.Unmarshal(response, responseObject) 309 | } 310 | 311 | return responseObject, nil 312 | } 313 | --------------------------------------------------------------------------------