├── .gitignore ├── README.md ├── demo ├── demo_mock │ ├── first_version │ │ └── github.go │ └── second_version │ │ ├── github_testable.go │ │ └── github_testable_test.go ├── example01 │ ├── ex01 │ │ ├── hello.go │ │ └── hello_test.go │ ├── ex02 │ │ ├── add_test.go │ │ └── calculator.go │ ├── ex03 │ │ ├── demo_switch.go │ │ ├── loop.go │ │ ├── multiple_variable.go │ │ ├── my_constant.go │ │ ├── simple_type.go │ │ └── variable.go │ ├── ex04 │ │ ├── demo_array01.go │ │ ├── demo_map.go │ │ ├── demo_slice.go │ │ ├── demo_slice2.go │ │ ├── demo_slice_function.go │ │ └── demo_slice_function2.go │ ├── ex05 │ │ ├── demo_defer.go │ │ ├── demo_defer_file.go │ │ ├── demo_function.go │ │ ├── demo_function_multiple_return.go │ │ ├── demo_panic.go │ │ └── demo_variadic_function.go │ ├── ex06 │ │ ├── demo_struct.go │ │ └── workshop_struct.go │ └── ex07 │ │ └── demo_interface.go ├── example_package │ ├── build.bat │ ├── build.sh │ └── src │ │ └── demo │ │ ├── main.go │ │ └── math │ │ └── math.go ├── fizzbuzz │ ├── fizzbuzz.go │ └── fizzbuzz_test.go ├── hello-rest │ ├── hello │ │ └── demo_server_01.go │ └── hello_router │ │ ├── demo_router_01.go │ │ ├── demo_router_02.go │ │ ├── demo_router_03.go │ │ ├── demo_router_04.go │ │ └── demo_router_05.go ├── tdd │ ├── circular_buffer.go │ └── circular_buffer_test.go ├── thinknet01 │ ├── example_package │ │ └── src │ │ │ ├── demo │ │ │ └── main.go │ │ │ └── xxx │ │ │ ├── math.go │ │ │ └── xxx.go │ ├── hello.go │ ├── hello_test.go │ ├── human.go │ └── workshop │ │ ├── hello_rest │ │ └── helloworld.md │ │ ├── readmail │ │ ├── build.sh │ │ └── src │ │ │ └── readmail │ │ │ ├── better_dao │ │ │ ├── better.go │ │ │ └── better_test.go │ │ │ ├── dao │ │ │ ├── mongo.go │ │ │ └── mongo_test.go │ │ │ ├── main.go │ │ │ ├── model │ │ │ └── email.go │ │ │ └── parser │ │ │ ├── parser.go │ │ │ ├── parser_test.go │ │ │ └── testdata │ │ │ └── success │ │ ├── sample_api_testable │ │ └── src │ │ │ ├── api │ │ │ ├── api.go │ │ │ └── api_test.go │ │ │ ├── main │ │ │ └── main.go │ │ │ └── model │ │ │ ├── user.go │ │ │ └── user_test.go │ │ ├── sample_network │ │ ├── better_reader_test.go │ │ ├── step1 │ │ │ └── step1_bad_reader.go │ │ └── step2_better_reader.go │ │ └── sample_testable │ │ ├── user.go │ │ └── user_test.go ├── todo │ ├── build.bat │ ├── build.sh │ ├── src │ │ ├── server │ │ │ └── server.go │ │ └── task │ │ │ ├── task.go │ │ │ └── task_test.go │ └── testing │ │ └── todo_get_all.md └── ws_api │ ├── README.md │ ├── api.go │ ├── api.md │ ├── api2.go │ └── api_error.md ├── slide └── SCK-GOLANG-TDD-WITH-REST.pdf └── workshop ├── TEST.postman_collection.json ├── demo_package ├── build.sh └── src │ ├── main │ └── main.go │ └── mypackage │ └── add.go ├── hello ├── hello.go └── hello_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | golang.org 2 | gopkg.in 3 | github.com 4 | .idea/ 5 | bin 6 | pkg 7 | *.out 8 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 9 | *.o 10 | *.a 11 | *.so 12 | 13 | # Folders 14 | _obj 15 | _test 16 | 17 | # Architecture specific extensions/prefixes 18 | *.[568vq] 19 | [568vq].out 20 | 21 | *.cgo1.go 22 | *.cgo2.c 23 | _cgo_defun.c 24 | _cgo_gotypes.go 25 | _cgo_export.* 26 | 27 | _testmain.go 28 | 29 | *.exe 30 | *.test 31 | *.prof 32 | .idea/ 33 | vendor/ 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # course-golang-tdd 2 | 3 | ### Course TDD with Golang 4 | 5 | * Basic of Go 6 | * Basic of TDD 7 | * Struct and Interface 8 | * Building REST APIs with Go 9 | * Testing REST APIs with Silk 10 | 11 | 12 | 13 | ### Resources 14 | * https://github.com/golang/go 15 | * https://github.com/avelino/awesome-go 16 | * https://github.com/golang/go/wiki/SliceTricks 17 | -------------------------------------------------------------------------------- /demo/demo_mock/first_version/github.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "io/ioutil" 7 | "encoding/json" 8 | "os" 9 | ) 10 | 11 | type ReleaseInfomation struct { 12 | Id int `json:"id"` 13 | TagName string `json:"tag_name"` 14 | } 15 | 16 | func getLatestTag(repo string) (string, error) { 17 | apiUrl := fmt.Sprintf("https://api.github.com/repos/%s/releases", repo) 18 | response, err := http.Get(apiUrl) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | defer response.Body.Close() 24 | 25 | body, err := ioutil.ReadAll(response.Body) 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | releases := []ReleaseInfomation{} 31 | 32 | if err := json.Unmarshal(body, &releases); err != nil { 33 | return "", err 34 | } 35 | 36 | return releases[0].TagName, nil 37 | } 38 | 39 | func main() { 40 | latestTag, err := getLatestTag("docker/machine") 41 | if err != nil { 42 | fmt.Println(os.Stderr, err) 43 | } 44 | 45 | fmt.Println(latestTag) 46 | } 47 | -------------------------------------------------------------------------------- /demo/demo_mock/second_version/github_testable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "net/http" 7 | "io/ioutil" 8 | "encoding/json" 9 | ) 10 | 11 | type Release interface { 12 | GetLatestTag(string) (string, error) 13 | } 14 | 15 | type ReleaseInformation struct { 16 | Id int `json:"id"` 17 | TagName string `json:"tag_name"` 18 | } 19 | 20 | type GithubRelease struct { 21 | } 22 | 23 | func (github GithubRelease) GetLatestTag(repo string) (string, error) { 24 | apiUrl := fmt.Sprintf("https://api.github.com/repos/%s/releases", repo) 25 | response, err := http.Get(apiUrl) 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | defer response.Body.Close() 31 | 32 | body, err := ioutil.ReadAll(response.Body) 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | releases := []ReleaseInformation{} 38 | 39 | if err := json.Unmarshal(body, &releases); err != nil { 40 | return "", err 41 | } 42 | 43 | return releases[0].TagName, nil 44 | } 45 | 46 | func getResult(r Release, repo string) (string, error) { 47 | tag, err := r.GetLatestTag(repo) 48 | if err != nil { 49 | return "", fmt.Errorf("Error from github %s", err) 50 | } 51 | 52 | return tag, nil 53 | } 54 | 55 | 56 | 57 | func main() { 58 | github := GithubRelease{} 59 | latestTag, err := getResult( github, "docker/machine") 60 | if err != nil { 61 | fmt.Println(os.Stderr, err) 62 | } 63 | 64 | fmt.Println(latestTag) 65 | } 66 | -------------------------------------------------------------------------------- /demo/demo_mock/second_version/github_testable_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | type FakeRelease struct { 6 | Tag string 7 | Err error 8 | } 9 | 10 | func (f FakeRelease) GetLatestTag(repo string) (string, error) { 11 | if f.Err != nil { 12 | return "", f.Err 13 | } 14 | 15 | return f.Tag, nil 16 | 17 | } 18 | 19 | func TestGetLatestTag(t *testing.T) { 20 | f := FakeRelease{ 21 | Tag: "1.0.0", 22 | Err: nil, 23 | } 24 | 25 | expected := "1.0.0" 26 | tag, _ := getResult(f, "xxx") 27 | if tag != expected { 28 | t.Fatalf("Expectd %s but got %s", expected, tag) 29 | } 30 | } -------------------------------------------------------------------------------- /demo/example01/ex01/hello.go: -------------------------------------------------------------------------------- 1 | package ex01 2 | 3 | func hello() string { 4 | return "Hello my first testing" 5 | } 6 | -------------------------------------------------------------------------------- /demo/example01/ex01/hello_test.go: -------------------------------------------------------------------------------- 1 | package ex01 2 | 3 | import( 4 | "testing" 5 | ) 6 | 7 | func TestHello(t *testing.T) { 8 | expectedResult := "Hello my first testing" 9 | result := hello() 10 | if result != expectedResult { 11 | t.Fatalf("Expected %s but got %s", expectedResult, result) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/example01/ex02/add_test.go: -------------------------------------------------------------------------------- 1 | package ex02 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAdd(t *testing.T) { 8 | var dataTests = []struct{ 9 | op1 int 10 | op2 int 11 | expectedResult int 12 | }{ 13 | {1, 2, 3}, 14 | {5, 10, 15}, 15 | {10, -5, 5}, 16 | } 17 | 18 | for _, test := range dataTests{ 19 | result := add(test.op1, test.op2) 20 | if result != test.expectedResult { 21 | t.Fatalf("Expected %d but got %d", test.expectedResult, result) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/example01/ex02/calculator.go: -------------------------------------------------------------------------------- 1 | package ex02 2 | 3 | 4 | func add(op1 int, op2 int) int { 5 | return op1 + op2 6 | } 7 | -------------------------------------------------------------------------------- /demo/example01/ex03/demo_switch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | i := 1 7 | 8 | switch i { 9 | case 0: 10 | fmt.Println("case 0") 11 | case 1: 12 | fmt.Println("case 1") 13 | case 2: 14 | fmt.Println("case 2") 15 | case 3: 16 | fmt.Println("case 2") 17 | default: 18 | fmt.Println("Unknow number") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/example01/ex03/loop.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | 7 | for i := 0; i<10; i++ { 8 | fmt.Println(i) 9 | } 10 | 11 | var i = 0 12 | for i < 10 { 13 | fmt.Println(i) 14 | i++ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/example01/ex03/multiple_variable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a, b, c = 1, 2, 3 7 | 8 | fmt.Println(a, b, c) 9 | } 10 | -------------------------------------------------------------------------------- /demo/example01/ex03/my_constant.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | const name string = "Somkiat" 7 | fmt.Println(name) 8 | } -------------------------------------------------------------------------------- /demo/example01/ex03/simple_type.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | i := 1 7 | f := 1.0 8 | s := "Hello" 9 | b := true 10 | fmt.Printf("Type %T has value %d\n", i, i) 11 | fmt.Printf("Type %T has value %.2f\n", f, f) 12 | fmt.Printf("Type %T has value %s\n", s, s) 13 | fmt.Printf("Type %T has value %v\n", b, b) 14 | } 15 | -------------------------------------------------------------------------------- /demo/example01/ex03/variable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a string = "first" 7 | 8 | var b string 9 | b = "second" 10 | 11 | var c = "third" 12 | 13 | d := "forth" 14 | 15 | fmt.Println(a) 16 | fmt.Println(b) 17 | fmt.Println(c) 18 | fmt.Println(d) 19 | } 20 | -------------------------------------------------------------------------------- /demo/example01/ex04/demo_array01.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | var data [10]string 10 | for i := 0; i 1 && n[0] > n[1] { 5 | n[0], n[1] = n[1], n[0] 6 | } 7 | 8 | if len(n) > 2 && n[1] > n[2] { 9 | n[1], n[2] = n[2], n[1] 10 | } 11 | 12 | return n 13 | } -------------------------------------------------------------------------------- /demo/thinknet01/hello_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "reflect" 6 | ) 7 | 8 | func TestCase1(t *testing.T) { 9 | expected := []int{1} 10 | result := sort(1) 11 | if !reflect.DeepEqual(expected, result) { 12 | t.Fatalf("Expected %v but got %v", expected, result) 13 | } 14 | } 15 | 16 | func TestCase2(t *testing.T) { 17 | expected := []int{1, 2} 18 | result := sort(1, 2) 19 | if !reflect.DeepEqual(expected, result) { 20 | t.Fatalf("Expected %v but got %v", expected, result) 21 | } 22 | } 23 | 24 | func TestCase3(t *testing.T) { 25 | expected := []int{1, 2} 26 | result := sort(2, 1) 27 | if !reflect.DeepEqual(expected, result) { 28 | t.Fatalf("Expected %v but got %v", expected, result) 29 | } 30 | } 31 | 32 | func TestCase4(t *testing.T) { 33 | expected := []int{1, 2, 3} 34 | result := sort(1, 2, 3) 35 | if !reflect.DeepEqual(expected, result) { 36 | t.Fatalf("Expected %v but got %v", expected, result) 37 | } 38 | } 39 | 40 | func TestCase5(t *testing.T) { 41 | expected := []int{1, 2, 3} 42 | result := sort(2, 1, 3) 43 | if !reflect.DeepEqual(expected, result) { 44 | t.Fatalf("Expected %v but got %v", expected, result) 45 | } 46 | } 47 | 48 | func TestCase6(t *testing.T) { 49 | expected := []int{1, 2, 3} 50 | result := sort(1, 3, 2) 51 | if !reflect.DeepEqual(expected, result) { 52 | t.Fatalf("Expected %v but got %v", expected, result) 53 | } 54 | } 55 | 56 | func TestCase7(t *testing.T) { 57 | expected := []int{1, 2, 3} 58 | result := sort(3, 2, 1) 59 | if !reflect.DeepEqual(expected, result) { 60 | t.Fatalf("Expected %v but got %v", expected, result) 61 | } 62 | } 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | func checkResult(expected, result []int) bool { 71 | if result == nil { 72 | return false 73 | } 74 | 75 | if len(result) != len(expected) { 76 | return false 77 | } 78 | 79 | for i, _ := range expected { 80 | if expected[i] != result[i] { 81 | return false 82 | } 83 | } 84 | 85 | return true 86 | 87 | //return reflect.DeepEqual(expected, result) 88 | } 89 | 90 | -------------------------------------------------------------------------------- /demo/thinknet01/human.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Human struct { 6 | firstname string 7 | lastname string 8 | age int 9 | } 10 | 11 | func (h Human) eat() { 12 | fmt.Println("Human can eat!! ", h) 13 | } 14 | 15 | func NewFullHuman(f string, l string, a int) Human { 16 | return Human{f, l, a} 17 | } 18 | 19 | func main() { 20 | h1 := Human{firstname:"somkiat"} 21 | h1.eat() 22 | } 23 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/hello_rest/helloworld.md: -------------------------------------------------------------------------------- 1 | # หน้าแรก 2 | คำอธิบายต่าง ๆ 3 | 4 | ## GET / 5 | Hello world API na 6 | sdfsdf 7 | 8 | --- 9 | 10 | ### Success response 11 | ต้องสำเร็จนะ 12 | 13 | * Status: 200 14 | 15 | ``` 16 | Hello world. 17 | ``` 18 | 19 | ## GET /fail 20 | Fail API na 21 | sdfsdf 22 | 23 | --- 24 | 25 | ### Fail response 26 | ต้อง fail นะ 27 | 28 | * Status: 200 29 | 30 | ``` 31 | Fail na 32 | ``` 33 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GOPATH=`pwd` 3 | rm -rf bin 4 | 5 | # go get -u github.com/jhillyerd/enmime 6 | # git clone -b v2 git@github.com:go-mgo/mgo.git $GOPATH/src/gopkg.in/mgo.v2 7 | 8 | gofmt -w src/readmail 9 | go test -v --cover $(go list ./... | grep -v github.com | grep -v golang.org | grep -v gopkg.in) 10 | go install readmail 11 | ./bin/readmail 12 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/better_dao/better.go: -------------------------------------------------------------------------------- 1 | package better_dao 2 | 3 | import ( 4 | "gopkg.in/mgo.v2" 5 | "readmail/model" 6 | "readmail/parser" 7 | ) 8 | 9 | type Mongo struct { 10 | session *mgo.Session 11 | dbName string 12 | } 13 | 14 | func (m Mongo) db() *mgo.Database { 15 | session := m.session.Copy() 16 | session.SetMode(mgo.Monotonic, true) 17 | return session.DB(m.dbName) 18 | } 19 | 20 | func (m Mongo) Save(mail parser.Mail) error { 21 | err := m.db().C("mail").Insert(&model.XMail{From: mail.From, To: mail.To, Subject: mail.Subject}) 22 | return err 23 | } 24 | 25 | func (m Mongo) drop() error { 26 | err := m.db().DropDatabase() 27 | return err 28 | } 29 | 30 | func NewMongo(dbURI, dbName string) (Mongo, error) { 31 | session, err := mgo.Dial(dbURI) 32 | return Mongo{ 33 | session: session, 34 | dbName: dbName, 35 | }, err 36 | } 37 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/better_dao/better_test.go: -------------------------------------------------------------------------------- 1 | package better_dao 2 | 3 | import ( 4 | "readmail/parser" 5 | "testing" 6 | ) 7 | 8 | var mongo Mongo 9 | 10 | func init() { 11 | mongo, _ = NewMongo("127.0.0.1", "testing") 12 | } 13 | 14 | func TestBetterSaveDataToMongoDBSuccess(t *testing.T) { 15 | mongo.drop() 16 | 17 | m := parser.Mail{} 18 | result := mongo.Save(m) 19 | if result != nil { 20 | t.Fatalf("Can not save data to mongodb") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/dao/mongo.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "gopkg.in/mgo.v2" 5 | "gopkg.in/mgo.v2/bson" 6 | "readmail/model" 7 | "readmail/parser" 8 | ) 9 | 10 | func Save(m parser.Mail) error { 11 | session, err := mgo.Dial("127.0.0.1") 12 | if err != nil { 13 | return err 14 | } 15 | defer session.Close() 16 | 17 | session.SetMode(mgo.Monotonic, true) 18 | c := session.DB("test").C("mail") 19 | 20 | err = c.Insert(&model.XMail{From: m.From, To: m.To, Subject: m.Subject}) 21 | return err 22 | } 23 | 24 | func Find() ([]model.XMail, error) { 25 | session, err := mgo.Dial("127.0.0.1") 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer session.Close() 30 | 31 | session.SetMode(mgo.Monotonic, true) 32 | c := session.DB("test").C("mail") 33 | 34 | query := bson.M{} 35 | var result []model.XMail 36 | err = c.Find(query).All(&result) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return result, nil 41 | } 42 | 43 | func drop() { 44 | session, err := mgo.Dial("127.0.0.1") 45 | if err != nil { 46 | panic(err) 47 | } 48 | defer session.Close() 49 | 50 | session.SetMode(mgo.Monotonic, true) 51 | err = session.DB("test").DropDatabase() 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | func count() (int, error) { 58 | session, err := mgo.Dial("127.0.0.1") 59 | if err != nil { 60 | return 0, err 61 | } 62 | defer session.Close() 63 | 64 | session.SetMode(mgo.Monotonic, true) 65 | c := session.DB("test").C("mail") 66 | return c.Count() 67 | } 68 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/dao/mongo_test.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "readmail/parser" 5 | "testing" 6 | ) 7 | 8 | func TestSaveDataToMongoDBSuccess(t *testing.T) { 9 | drop() 10 | 11 | m := parser.Mail{} 12 | result := Save(m) 13 | if result != nil { 14 | t.Fatalf("Can not save data to mongodb") 15 | } 16 | } 17 | 18 | func TestSaveAndCountData(t *testing.T) { 19 | drop() 20 | 21 | m := parser.Mail{From: "somkiat.p@gmail.com"} 22 | Save(m) 23 | 24 | count, err := count() 25 | if count != 1 { 26 | t.Fatalf("Can not find data to mongodb with %v", err) 27 | } 28 | } 29 | 30 | func TestSaveAndFindData(t *testing.T) { 31 | drop() 32 | 33 | m := parser.Mail{From: "somkiat.p@gmail.com"} 34 | Save(m) 35 | 36 | xm, err := Find() 37 | if err != nil { 38 | t.Fatalf("Can not find data to mongodb with %v", err) 39 | } 40 | 41 | if xm[0].From != m.From { 42 | t.Fatalf("Incorrect From %v", xm[0].From) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "readmail/dao" 6 | "readmail/parser" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Start read mail") 11 | m := parser.ReadFromFile("sample/1489731215.Vfd00I80501M718280.jobthai-mail01") 12 | fmt.Println(m.From, m.To, m.Subject) 13 | dao.Save(parser.Mail{}) 14 | } 15 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/model/email.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | "time" 6 | ) 7 | 8 | type XMail struct { 9 | ID bson.ObjectId `bson:"_id,omitempty"` 10 | From string 11 | To string 12 | Subject string 13 | Timestamp time.Time 14 | } 15 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/jhillyerd/enmime" 5 | "os" 6 | ) 7 | 8 | type Mail struct { 9 | From string 10 | To string 11 | Subject string 12 | } 13 | 14 | func ReadFromFile(path string) Mail { 15 | file, err := os.Open(path) 16 | defer file.Close() 17 | if err != nil { 18 | panic(err) 19 | } 20 | env, err := enmime.ReadEnvelope(file) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | m := Mail{} 26 | m.From = env.GetHeader("From") 27 | alist, _ := env.AddressList("To") 28 | for _, addr := range alist { 29 | // fmt.Printf("To: %s <%s>\n", addr.Name, addr.Address) 30 | m.To = addr.Address 31 | } 32 | m.Subject = env.GetHeader("Subject") 33 | return m 34 | } 35 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/readmail/src/readmail/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | func TestReadGoodFile(t *testing.T) { 9 | expected := Mail{} 10 | expected.From = "" 11 | expected.To = "proptech11_252@trustmail.jobthai.com" 12 | expected.Subject = "สมัครงาน" 13 | 14 | path := filepath.Join("testdata", "success") 15 | actual := ReadFromFile(path) 16 | if actual != expected { 17 | t.Fatalf("Expected %v but got %v", expected, actual) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_api_testable/src/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/gorilla/mux" 6 | "net/http" 7 | 8 | "model" 9 | ) 10 | 11 | func Handlers() *mux.Router { 12 | router := mux.NewRouter() 13 | router.HandleFunc("/users", listUsersHandler).Methods("GET") 14 | return router 15 | } 16 | 17 | func listUsersHandler(w http.ResponseWriter, r *http.Request) { 18 | users := [2]model.User{} 19 | users[0] = model.User{1, "user 01", "last 01", 20} 20 | users[1] = model.User{2, "user 02", "last 02", 30} 21 | 22 | res, _ := json.Marshal(users) 23 | _, err := w.Write(res) 24 | if err != nil { 25 | panic(err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_api_testable/src/api/api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | var ( 13 | server *httptest.Server 14 | reader io.Reader 15 | usersUrl string 16 | ) 17 | 18 | func init() { 19 | server = httptest.NewServer(Handlers()) 20 | usersUrl = fmt.Sprintf("%s/users", server.URL) 21 | } 22 | 23 | func TestListUsersWithSuccess200(t *testing.T) { 24 | reader = strings.NewReader("") 25 | request, err := http.NewRequest("GET", usersUrl, reader) 26 | res, err := http.DefaultClient.Do(request) 27 | 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | if res.StatusCode != 200 { 33 | t.Errorf("Success expected: %d", res.StatusCode) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_api_testable/src/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "api" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("Server starting") 12 | http.ListenAndServe(":8080", api.Handlers()) 13 | } 14 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_api_testable/src/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type User struct { 4 | Id int `json:"user_id"` 5 | Firstname string `json:"fname"` 6 | Lastname string `json:"lname"` 7 | Age int `json:"age"` 8 | } 9 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_api_testable/src/model/user_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUser(t *testing.T) { 8 | var user = User{1, "somkiat", "pui", 30} 9 | if user == (User{}) { 10 | t.Fatal("Can not create") 11 | } 12 | 13 | if user.Id != 1 { 14 | t.Fatalf("Wrong of creation") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_network/better_reader_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type MockRepoInfoer struct{} 8 | 9 | func (mr MockRepoInfoer) GetRepoInfoFromAPI(repo string) (string, error) { 10 | return repo, nil 11 | } 12 | 13 | func TestGetFullnamefromGithubRepository(t *testing.T) { 14 | expectedFullname := "demo/test" 15 | mock := MockRepoInfoer{} 16 | f, err := getRepoFullName(mock, expectedFullname) 17 | if err != nil { 18 | t.Fatalf("Got error %v", err) 19 | } 20 | 21 | if f != "Full name is demo/test" { 22 | t.Fatalf("Expected %v but got %v", expectedFullname, f) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_network/step1/step1_bad_reader.go: -------------------------------------------------------------------------------- 1 | package step1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | type RepoInfo struct { 11 | Id uint `json:"id"` 12 | FullName string `json:"full_name"` 13 | } 14 | 15 | func getRepoInfoFromAPI(repo string) (string, error) { 16 | apiUrl := fmt.Sprintf("https://api.github.com/repos/%s", repo) 17 | response, err := http.Get(apiUrl) 18 | if err != nil { 19 | return "", err 20 | } 21 | defer response.Body.Close() 22 | 23 | body, err := ioutil.ReadAll(response.Body) 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | info := RepoInfo{} 29 | 30 | if err := json.Unmarshal(body, &info); err != nil { 31 | return "", err 32 | } 33 | 34 | fullName := info.FullName 35 | 36 | return fullName, nil 37 | } 38 | 39 | func getRepoFullName(repo string) (string, error) { 40 | fullName, err := getRepoInfoFromAPI(repo) 41 | if err != nil { 42 | return "", fmt.Errorf("Error querying GitHub API: %s", err) 43 | } 44 | return fmt.Sprintf("Full name is %s", fullName), nil 45 | } 46 | 47 | func main() { 48 | fullName, err := getRepoFullName("golang/go") 49 | if err != nil { 50 | fmt.Printf("Got error %v\n", err) 51 | } 52 | 53 | fmt.Println(fullName) 54 | } 55 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_network/step2_better_reader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | type RepoInfo struct { 11 | Id uint `json:"id"` 12 | FullName string `json:"full_name"` 13 | } 14 | 15 | //Interface 16 | type RepoInfoer interface { 17 | GetRepoInfoFromAPI(repo string) (string, error) 18 | } 19 | 20 | //Struct 21 | type GithubRepoInfoer struct{} 22 | 23 | func (gh GithubRepoInfoer) GetRepoInfoFromAPI(repo string) (string, error) { 24 | apiUrl := fmt.Sprintf("https://api.github.com/repos/%s", repo) 25 | response, err := http.Get(apiUrl) 26 | if err != nil { 27 | return "", err 28 | } 29 | defer response.Body.Close() 30 | 31 | body, err := ioutil.ReadAll(response.Body) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | info := RepoInfo{} 37 | 38 | if err := json.Unmarshal(body, &info); err != nil { 39 | return "", err 40 | } 41 | 42 | fullName := info.FullName 43 | 44 | return fullName, nil 45 | } 46 | 47 | func getRepoFullName(ri RepoInfoer, repo string) (string, error) { 48 | fullName, err := ri.GetRepoInfoFromAPI(repo) 49 | if err != nil { 50 | return "", fmt.Errorf("Error querying GitHub API: %s", err) 51 | } 52 | return fmt.Sprintf("Full name is %s", fullName), nil 53 | } 54 | 55 | func main() { 56 | gh := GithubRepoInfoer{} 57 | fullName, err := getRepoFullName(gh, "golang/go") 58 | if err != nil { 59 | fmt.Printf("Got error %v\n", err) 60 | } 61 | 62 | fmt.Println(fullName) 63 | } 64 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_testable/user.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Model 8 | type User struct { 9 | id int 10 | fname string 11 | lname string 12 | } 13 | 14 | type Creator interface { 15 | CreateUser() User 16 | } 17 | 18 | func (u User) CreateUser() User { 19 | fmt.Println("Call mongodb") 20 | return User{} 21 | } 22 | 23 | // Service 24 | func register(c Creator) { 25 | c.CreateUser() 26 | } 27 | 28 | 29 | func main() { 30 | register(User{}) 31 | } 32 | -------------------------------------------------------------------------------- /demo/thinknet01/workshop/sample_testable/user_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | ) 7 | 8 | type MockUser struct {} 9 | 10 | func (mu MockUser) CreateUser() User { 11 | fmt.Println("Call from mock user") 12 | return User{} 13 | } 14 | 15 | func TestCreateDataWithSuccess(t *testing.T) { 16 | register(MockUser{}) 17 | } 18 | -------------------------------------------------------------------------------- /demo/todo/build.bat: -------------------------------------------------------------------------------- 1 | set CURDIR=`pwd` 2 | set OLDGOPATH=%$GOPATH% 3 | set GOPATH=%cd% 4 | 5 | gofmt -w src/server 6 | 7 | go install task 8 | go install server 9 | 10 | set GOPATH=%OLDGOPATH% 11 | 12 | echo 'finished' -------------------------------------------------------------------------------- /demo/todo/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURDIR=`pwd` 4 | OLDGOPATH="$GOPATH" 5 | export GOPATH="$CURDIR" 6 | 7 | gofmt -w src/ 8 | 9 | go install task 10 | go install server 11 | 12 | go test ./... -v -cover 13 | 14 | export GOPATH="$OLDGOPATH" 15 | 16 | echo 'finished' -------------------------------------------------------------------------------- /demo/todo/src/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "regexp" 8 | "strconv" 9 | ) 10 | 11 | type route struct { 12 | pattern *regexp.Regexp 13 | verb string 14 | handler http.Handler 15 | } 16 | 17 | type RegexpHandler struct { 18 | routes []*route 19 | } 20 | 21 | func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) { 22 | h.routes = append(h.routes, &route{pattern, verb, handler}) 23 | } 24 | 25 | func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) { 26 | re := regexp.MustCompile(r) 27 | h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)}) 28 | } 29 | 30 | func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 31 | for _, route := range h.routes { 32 | if route.pattern.MatchString(r.URL.Path) && route.verb == r.Method { 33 | route.handler.ServeHTTP(w, r) 34 | return 35 | } 36 | } 37 | http.NotFound(w, r) 38 | } 39 | 40 | func main() { 41 | 42 | server := Server{} 43 | 44 | regHandler := new(RegexpHandler) 45 | 46 | regHandler.HandleFunc("/todo/$", "GET", server.listOfTODO) 47 | regHandler.HandleFunc("/todo/$", "POST", server.createTODO) 48 | regHandler.HandleFunc("/todo/[0-9]$", "GET", server.getTODOByID) 49 | //regHandler.HandleFunc("/todo/[0-9]$", "PUT", UpdateTODOByID) 50 | regHandler.HandleFunc("/todo/[0-9]$", "DELETE", server.deleteTODOByID) 51 | 52 | regHandler.HandleFunc("/", "GET", server.hello) 53 | 54 | http.ListenAndServe(":8080", regHandler) 55 | } 56 | 57 | type Todo struct { 58 | Id int `json:"id"` 59 | Title string `json:"title"` 60 | Done bool `json:"done"` 61 | } 62 | 63 | type Server struct { 64 | } 65 | 66 | func (s Server) hello(rw http.ResponseWriter, req *http.Request) { 67 | rw.Write([]byte("Hello world.")) 68 | } 69 | 70 | func (s Server) listOfTODO(res http.ResponseWriter, req *http.Request) { 71 | //Sample DATA 72 | todos := []Todo{ 73 | {Id: 1, Title: "Todo 1", Done: false}, 74 | {Id: 2, Title: "Todo 2", Done: false}, 75 | {Id: 3, Title: "Todo 3", Done: false}, 76 | } 77 | 78 | //Send JSON response 79 | res.Header().Set("Content-Type", "application/json; charset=utf-8") 80 | payload, err := json.Marshal(todos) 81 | if err != nil { 82 | http.Error(res, err.Error(), http.StatusInternalServerError) 83 | return 84 | } 85 | 86 | fmt.Fprintf(res, string(payload)) 87 | } 88 | 89 | func (s Server) createTODO(res http.ResponseWriter, req *http.Request) { 90 | res.Header().Set("Content-Type", "application/json; charset=utf-8") 91 | 92 | newTodo := &Todo{} 93 | decoder := json.NewDecoder(req.Body) 94 | err := decoder.Decode(&newTodo) 95 | if err != nil { 96 | fmt.Println("Error to decode json ", err) 97 | return 98 | } 99 | 100 | //Create new TODO 101 | fmt.Println(newTodo) 102 | 103 | //Send JSON response 104 | res.Header().Set("Content-Type", "application/json; charset=utf-8") 105 | payload, err := json.Marshal(newTodo) 106 | if err != nil { 107 | http.Error(res, err.Error(), http.StatusInternalServerError) 108 | return 109 | } 110 | 111 | fmt.Fprintf(res, string(payload)) 112 | } 113 | 114 | func (s Server) getTODOByID(res http.ResponseWriter, req *http.Request) { 115 | //Get id from uri 116 | r, _ := regexp.Compile(`\d+$`) 117 | id := r.FindString(req.URL.Path) 118 | todo_d, _ := strconv.Atoi(id) 119 | 120 | //Get TODO by ID 121 | todo := Todo{ 122 | Id: todo_d, 123 | Title: "XXXX", 124 | Done: true, 125 | } 126 | 127 | //Send JSON response 128 | res.Header().Set("Content-Type", "application/json; charset=utf-8") 129 | payload, err := json.Marshal(todo) 130 | if err != nil { 131 | http.Error(res, err.Error(), http.StatusInternalServerError) 132 | return 133 | } 134 | 135 | fmt.Fprintf(res, string(payload)) 136 | } 137 | 138 | func (s Server) deleteTODOByID(res http.ResponseWriter, req *http.Request) { 139 | //TODO 140 | res.WriteHeader(200) 141 | } 142 | -------------------------------------------------------------------------------- /demo/todo/src/task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "fmt" 4 | 5 | type Todo struct { 6 | ID int64 7 | Title string 8 | Done bool 9 | } 10 | 11 | func NewTodo(title string) (*Todo, error) { 12 | if title == "" { 13 | return nil, fmt.Errorf("Empty title") 14 | } 15 | return &Todo{0, title, false}, nil 16 | } 17 | 18 | type TodoManager struct { 19 | tasks []*Todo 20 | lastID int64 21 | } 22 | 23 | func NewTodoManager() *TodoManager { 24 | return &TodoManager{} 25 | } 26 | 27 | func (todoManager *TodoManager) save(task *Todo) error { 28 | if task.ID == 0 { 29 | todoManager.lastID++ 30 | task.ID = todoManager.lastID 31 | todoManager.tasks = append(todoManager.tasks, cloneTask(task)) 32 | return nil 33 | } 34 | 35 | for i, t := range todoManager.tasks { 36 | if t.ID == task.ID { 37 | todoManager.tasks[i] = cloneTask(task) 38 | return nil 39 | } 40 | } 41 | return fmt.Errorf("Unknown task") 42 | } 43 | 44 | func (todoManager *TodoManager) GetAll() []*Todo { 45 | return todoManager.tasks 46 | } 47 | 48 | func (todoManager *TodoManager) Find(ID int64) (*Todo, bool) { 49 | for _, task := range todoManager.tasks { 50 | if task.ID == ID { 51 | return cloneTask(task), true 52 | } 53 | } 54 | return nil, false 55 | } 56 | 57 | func cloneTask(task *Todo) *Todo { 58 | copy := *task 59 | return © 60 | } 61 | -------------------------------------------------------------------------------- /demo/todo/src/task/task_test.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "testing" 4 | 5 | func TestNewTodoWithEmptyTitle(t *testing.T) { 6 | _, err := NewTodo("") 7 | if err == nil { 8 | t.Error("Expected 'Empty Title', error, got nil") 9 | } 10 | } 11 | 12 | func TestNewTodo(t *testing.T) { 13 | title := "My Title" 14 | todo, _ := NewTodo(title) 15 | if todo.Title != title { 16 | t.Errorf("Expected title %q, got %q", title, todo.Title) 17 | } 18 | if todo.Done { 19 | t.Errorf("New todo is done") 20 | } 21 | } 22 | 23 | func TestSaveOneTodoAndGetOne(t *testing.T) { 24 | todo, err := NewTodo("My todo") 25 | if err != nil { 26 | t.Errorf("New task : %v", err) 27 | } 28 | 29 | taskManager := NewTodoManager() 30 | taskManager.save(todo) 31 | 32 | allTask := taskManager.GetAll() 33 | if len(allTask) != 1 { 34 | t.Errorf("Expected 1 task, got %v", len(allTask)) 35 | } 36 | 37 | if *allTask[0] != *todo { 38 | t.Errorf("Expected %v, got %v", todo, allTask) 39 | } 40 | } 41 | 42 | func TestSaveTwoAndGetTwo(t *testing.T) { 43 | todo1, err := NewTodo("My todo 1") 44 | if err != nil { 45 | t.Errorf("New todo : %v", err) 46 | } 47 | 48 | todo2, err := NewTodo("My todo 2") 49 | if err != nil { 50 | t.Errorf("New todo : %v", err) 51 | } 52 | 53 | todoManager := NewTodoManager() 54 | todoManager.save(todo1) 55 | todoManager.save(todo2) 56 | 57 | allTodo := todoManager.GetAll() 58 | if len(allTodo) != 2 { 59 | t.Errorf("Expected 2 todo, got %v", len(allTodo)) 60 | } 61 | } 62 | 63 | func TestSaveUpdateWithDoneAndGet(t *testing.T) { 64 | todo, err := NewTodo("My todo") 65 | if err != nil { 66 | t.Errorf("New todo : %v", err) 67 | } 68 | 69 | todoManager := NewTodoManager() 70 | todoManager.save(todo) 71 | 72 | todo.Done = true 73 | 74 | allTodo := todoManager.GetAll() 75 | if allTodo[0].Done { 76 | t.Errorf("Save todo not done") 77 | } 78 | } 79 | 80 | func TestDuplicateSaveAndGetOne(t *testing.T) { 81 | todo, err := NewTodo("My todo") 82 | if err != nil { 83 | t.Errorf("New todo : %v", err) 84 | } 85 | 86 | todoManager := NewTodoManager() 87 | todoManager.save(todo) 88 | todoManager.save(todo) 89 | 90 | allTodo := todoManager.GetAll() 91 | if len(allTodo) != 1 { 92 | t.Errorf("Expected 1 todo, got %v", len(allTodo)) 93 | } 94 | } 95 | 96 | func TestSaveAndFindByID(t *testing.T) { 97 | todo, err := NewTodo("My task") 98 | if err != nil { 99 | t.Errorf("New task : %v", err) 100 | } 101 | 102 | todoManager := NewTodoManager() 103 | todoManager.save(todo) 104 | 105 | myTodo, ok := todoManager.Find(todo.ID) 106 | if !ok { 107 | t.Errorf("Todo not found") 108 | } 109 | 110 | if *todo != *myTodo { 111 | t.Errorf("Expected %v, got %v", todo, myTodo) 112 | } 113 | } 114 | 115 | func TestSaveFindAndEdit(t *testing.T) { 116 | todo, err := NewTodo("My todo") 117 | if err != nil { 118 | t.Errorf("New todo : %v", err) 119 | } 120 | 121 | todoManager := NewTodoManager() 122 | todoManager.save(todo) 123 | 124 | myTask, _ := todoManager.Find(todo.ID) 125 | myTask.Done = true 126 | todoManager.save(myTask) 127 | 128 | allTodo := todoManager.GetAll() 129 | if !allTodo[0].Done { 130 | t.Errorf("Save todo not done") 131 | } 132 | 133 | if len(allTodo) != 1 { 134 | t.Errorf("Expected 1 todo, got %v", len(allTodo)) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /demo/todo/testing/todo_get_all.md: -------------------------------------------------------------------------------- 1 | # Manage TODO 2 | 3 | * Root: "http://localhost:8080/" 4 | 5 | ## `POST /todo/` 6 | 7 | Create new todo 8 | 9 | ### Example request 10 | 11 | ```json 12 | { 13 | "title": "TODO 01" 14 | } 15 | ``` 16 | 17 | * `Content-Type`: "application/json" 18 | 19 | === 20 | 21 | ### Example response 22 | 23 | * `Status`: `200` 24 | * `Content-Type`: `"application/json; charset=utf-8"` 25 | 26 | ```json 27 | { 28 | "id": 0, 29 | "title": "TODO 01", 30 | "done": false 31 | } 32 | ``` 33 | 34 | ## `GET` `/todo/1` 35 | 36 | Get todo by id 37 | 38 | === 39 | 40 | * `Status`: `200` 41 | * `Content-Type`: `"application/json; charset=utf-8"` 42 | 43 | ```json 44 | { 45 | "id": 1, 46 | "title": "XXXX", 47 | "done": true 48 | } 49 | ``` 50 | 51 | ## `DELETE /todo/1` 52 | 53 | Delete todo by id 54 | 55 | === 56 | 57 | * `Status`: `200` // OK -------------------------------------------------------------------------------- /demo/ws_api/README.md: -------------------------------------------------------------------------------- 1 | # Testing APIs with [silk](https://github.com/matryer/silk) 2 | 3 | 4 | Install and run silk 5 | 6 | ``` 7 | $go get go get github.com/matryer/silk 8 | $export PATH=.:$GOPATH/bin:$PATH 9 | $silk 10 | 11 | ``` 12 | 13 | How to run ? 14 | ``` 15 | $silk -silk.url=http://localhost:8080/ api.md 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /demo/ws_api/api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | http.HandleFunc("/", hello) 10 | http.HandleFunc("/hello", hello2) 11 | err := http.ListenAndServe(":8080", nil) 12 | if err != nil { 13 | fmt.Println("Error with ", err) 14 | } 15 | } 16 | 17 | func hello2(res http.ResponseWriter, req *http.Request) { 18 | if req.Method == "POST" { 19 | fmt.Fprintf(res, "Call with %v", req.Method) 20 | // TODO 21 | return 22 | } 23 | res.WriteHeader(http.StatusBadRequest) 24 | 25 | } 26 | 27 | func hello(res http.ResponseWriter, req *http.Request) { 28 | res.Write([]byte("Hello World")) 29 | fmt.Fprintf(res, "Hello World") 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/ws_api/api.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## GET /todo/ 4 | ดึงข้อมูลของ todo ทั้งหมด 5 | 6 | === 7 | ### Response 8 | 9 | * Status: 200 10 | * Content-Type: "application/json; charset=utf-8" 11 | * Data[0].id: 1 12 | * Data[0].title: "Todo 1" 13 | 14 | ```json 15 | [ 16 | {"id":1,"title":"Todo 1","done":false}, 17 | {"id":2,"title":"Todo 2","done":false}, 18 | {"id":3,"title":"Todo 3","done":false}] 19 | ``` 20 | 21 | ## POST /todo/ 22 | ทำการสร้าง TODO ใหม่ 23 | 24 | ### Request 25 | 26 | ```json 27 | { 28 | "title": "TODO 01" 29 | } 30 | ``` 31 | 32 | * Content-Type: "application/json" 33 | 34 | === 35 | ### Response 36 | 37 | * Status: 200 38 | * Content-Type: "application/json; charset=utf-8" 39 | 40 | ```json 41 | { 42 | "id": 0, 43 | "title": "TODO 01", 44 | "done": false 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /demo/ws_api/api2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "regexp" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | type route struct { 11 | pattern *regexp.Regexp 12 | verb string 13 | handler http.Handler 14 | } 15 | 16 | type RegexpHandler struct { 17 | routes []*route 18 | } 19 | 20 | func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) { 21 | h.routes = append(h.routes, &route{pattern, verb, handler}) 22 | } 23 | 24 | func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) { 25 | re := regexp.MustCompile(r) 26 | h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)}) 27 | } 28 | 29 | func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 30 | for _, route := range h.routes { 31 | if route.pattern.MatchString(r.URL.Path) && route.verb == r.Method { 32 | route.handler.ServeHTTP(w, r) 33 | return 34 | } 35 | } 36 | http.NotFound(w, r) 37 | } 38 | 39 | func main() { 40 | 41 | server := Server{} 42 | 43 | regHandler := new(RegexpHandler) 44 | 45 | regHandler.HandleFunc("/todo/$", "GET", server.listOfTODO) 46 | regHandler.HandleFunc("/todo/$", "POST", server.createTODO) 47 | //regHandler.HandleFunc("/todo/[0-9]$", "GET", GetTODOByID) 48 | //regHandler.HandleFunc("/todo/[0-9]$", "PUT", UpdateTODOByID) 49 | //regHandler.HandleFunc("/todo/[0-9]$", "DELETE", DeleteTODOByID) 50 | 51 | regHandler.HandleFunc("/", "GET", server.hello) 52 | 53 | http.ListenAndServe(":8080", regHandler) 54 | } 55 | 56 | type Todo struct { 57 | Id int `json:"id"` 58 | Title string `json:"title"` 59 | Done bool `json:"done"` 60 | } 61 | 62 | type Server struct { 63 | } 64 | 65 | func (s Server) hello(rw http.ResponseWriter, req *http.Request) { 66 | rw.Write([]byte("Hello world.")) 67 | } 68 | 69 | func (s Server) listOfTODO(res http.ResponseWriter, req *http.Request) { 70 | //Sample DATA 71 | todos := []Todo{ 72 | { Id: 1, Title: "Todo 1", Done: false }, 73 | { Id: 2, Title: "Todo 2", Done: false }, 74 | { Id: 3, Title: "Todo 3", Done: false }, 75 | } 76 | 77 | //Send JSON response 78 | res.Header().Set("Content-Type", "application/json; charset=utf-8") 79 | payload, err := json.Marshal(todos) 80 | if err != nil { 81 | http.Error(res, err.Error(), http.StatusInternalServerError) 82 | return 83 | } 84 | 85 | fmt.Fprintf(res, string(payload)) 86 | } 87 | 88 | func (s Server) createTODO(res http.ResponseWriter, req *http.Request) { 89 | newTodo := &Todo{} 90 | decoder := json.NewDecoder(req.Body) 91 | err := decoder.Decode(&newTodo) 92 | if err != nil { 93 | fmt.Println("Error to decode json ", err) 94 | res.WriteHeader(http.StatusBadRequest) 95 | return 96 | } 97 | 98 | //Create new TODO 99 | fmt.Println(newTodo) 100 | 101 | //Send JSON response 102 | res.Header().Set("Content-Type", "application/json; charset=utf-8") 103 | payload, err := json.Marshal(newTodo) 104 | if err != nil { 105 | http.Error(res, err.Error(), http.StatusInternalServerError) 106 | return 107 | } 108 | 109 | fmt.Fprintf(res, string(payload)) 110 | } -------------------------------------------------------------------------------- /demo/ws_api/api_error.md: -------------------------------------------------------------------------------- 1 | # New group 2 | 3 | ## POST /todo/ 4 | ทำการสร้าง TODO ใหม่ไม่ได้ เพราะว่าไม่ส่งข้อมูลไป 5 | 6 | === 7 | ### Response 8 | 9 | * Status: 400 -------------------------------------------------------------------------------- /slide/SCK-GOLANG-TDD-WITH-REST.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/up1/course-golang-tdd/269b9dae415c92993c379686485f9c4b39f53623/slide/SCK-GOLANG-TDD-WITH-REST.pdf -------------------------------------------------------------------------------- /workshop/TEST.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "15f154c8-68fa-96d9-1e18-a86491f147a3", 3 | "name": "TEST", 4 | "description": "", 5 | "order": [ 6 | "751b1433-9801-a073-abb1-115c826263cb", 7 | "a20db3fa-d934-9cd6-fd00-cc07b196c5c0" 8 | ], 9 | "folders": [], 10 | "timestamp": 1497933897841, 11 | "owner": "1453931", 12 | "public": false, 13 | "requests": [ 14 | { 15 | "id": "751b1433-9801-a073-abb1-115c826263cb", 16 | "headers": "Content-Type: application/json\n", 17 | "headerData": [ 18 | { 19 | "key": "Content-Type", 20 | "value": "application/json", 21 | "description": "", 22 | "enabled": true 23 | } 24 | ], 25 | "url": "http://localhost:8080/todo/", 26 | "queryParams": [], 27 | "pathVariables": {}, 28 | "pathVariableData": [], 29 | "preRequestScript": null, 30 | "method": "GET", 31 | "collectionId": "15f154c8-68fa-96d9-1e18-a86491f147a3", 32 | "data": [], 33 | "dataMode": "raw", 34 | "name": "http://localhost:8080/todo/", 35 | "description": "", 36 | "descriptionFormat": "html", 37 | "time": 1497933897840, 38 | "version": 2, 39 | "responses": [], 40 | "tests": "tests[\"Status code is 200\"] = responseCode.code === 200;\n\n\n\nvar jsonData = JSON.parse(responseBody);\ntests[\"Size of data must 2 but have \" + jsonData.length] = jsonData.length === 2;\n\ntests[\"ข้อมูลชุดที่ 1 ต้องชื่อว่า Todo 1\"] = jsonData[0].title === \"Todo 1\";", 41 | "currentHelper": "normal", 42 | "helperAttributes": {}, 43 | "rawModeData": "{\n \"title\": \"test\",\n \"done\": true\n}" 44 | }, 45 | { 46 | "id": "a20db3fa-d934-9cd6-fd00-cc07b196c5c0", 47 | "headers": "", 48 | "headerData": [], 49 | "url": "http://localhost:8080/todo/", 50 | "queryParams": [], 51 | "pathVariables": {}, 52 | "pathVariableData": [], 53 | "preRequestScript": null, 54 | "method": "POST", 55 | "collectionId": "15f154c8-68fa-96d9-1e18-a86491f147a3", 56 | "data": [], 57 | "dataMode": "raw", 58 | "name": "http://localhost:8080/todo/", 59 | "description": "", 60 | "descriptionFormat": "html", 61 | "time": 1497942581245, 62 | "version": 2, 63 | "responses": [], 64 | "tests": null, 65 | "currentHelper": "normal", 66 | "helperAttributes": {}, 67 | "rawModeData": "{\n \"title\": \"My new todo\",\n \"done\": true\n}" 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /workshop/demo_package/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURDIR=`pwd` 4 | export GOPATH="$CURDIR" 5 | 6 | gofmt -w src/ 7 | go install mypackage 8 | go install main 9 | -------------------------------------------------------------------------------- /workshop/demo_package/src/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | cal "mypackage" 6 | ) 7 | 8 | func main() { 9 | fmt.Println(cal.Add(1, 2)) 10 | fmt.Println(cal.Add(2, 2)) 11 | } 12 | -------------------------------------------------------------------------------- /workshop/demo_package/src/mypackage/add.go: -------------------------------------------------------------------------------- 1 | package mypackage 2 | 3 | func Add(op1 int, op2 int) int { 4 | return op1 + op2 5 | } 6 | -------------------------------------------------------------------------------- /workshop/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/up1/course-golang-tdd/269b9dae415c92993c379686485f9c4b39f53623/workshop/hello -------------------------------------------------------------------------------- /workshop/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func sayHi() string { 4 | return "Hello World" 5 | } 6 | -------------------------------------------------------------------------------- /workshop/hello_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestReturnHelloWorldWhenSth(t *testing.T) { 8 | e := "Hello World" 9 | a := sayHi() //Action 10 | //Assert or Checking 11 | if(a != e) { 12 | t.Fatalf("Expected %s but got %s", e, a) 13 | } 14 | } 15 | --------------------------------------------------------------------------------