diff --git a/.circleci/config.yml b/.circleci/config.yml index ef38b6b..7781144 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,6 +25,6 @@ jobs: key: glide-cache-{{ checksum "glide.lock" }} paths: vendor/ - - run: go get -v -t -d ./... + - run: glide install - run: go build - run: go test -v $(go list ./... | grep -v /vendor/) diff --git a/_examples/events.go b/_examples/events.go new file mode 100644 index 0000000..52f8c19 --- /dev/null +++ b/_examples/events.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + + "github.com/g0tiu5a/ctftime" +) + +func main() { + url := ctftime.GetUrl("events", nil) + fmt.Printf("[==>] Requesting %s ...\n", url) + events := ctftime.GetAPIData("events", nil) + for idx, event := range events.([]ctftime.Event) { + fmt.Printf("[event%d]\n", idx) + fmt.Printf("%#v\n", event) + } +} diff --git a/api.go b/api.go new file mode 100644 index 0000000..2b47c72 --- /dev/null +++ b/api.go @@ -0,0 +1,52 @@ +package ctftime + +import ( + "log" +) + +type apiClient interface { + GetUrl() string + GetAPIData() interface{} +} + +type apiContext map[string]interface{} +type apiClientFactory func(ctx apiContext) apiClient + +var apiClientFactories = make(map[string]apiClientFactory) + +func registerAPIClient(name string, factory apiClientFactory) { + if factory == nil { + log.Panicf("API Client Factory %s does not exist.", name) + } + + _, registered := apiClientFactories[name] + if registered { + log.Panicf("API Client Factory %s already registered. Ignoring.", name) + } + + apiClientFactories[name] = factory +} + +// この関数はパッケージがimportされた時に呼び出されます +func init() { + registerAPIClient("events", newEventsAPIClient) +} + +func newAPIClient(name string, ctx apiContext) apiClient { + clientFactory, ok := apiClientFactories[name] + if !ok { + log.Panicf("Invalid API Client name!") + } + + return clientFactory(ctx) +} + +func GetUrl(name string, ctx apiContext) string { + client := newAPIClient(name, ctx) + return client.GetUrl() +} + +func GetAPIData(name string, ctx map[string]interface{}) interface{} { + client := newAPIClient(name, ctx) + return client.GetAPIData() +} diff --git a/api_test.go b/api_test.go new file mode 100644 index 0000000..6ba1f30 --- /dev/null +++ b/api_test.go @@ -0,0 +1,61 @@ +package ctftime + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "testing" +) + +func TestEventsAPIClient(t *testing.T) { + var client interface{} = newAPIClient("events", nil) + + if valid, ok := client.(apiClient); ok { + valid.GetUrl() + valid.GetAPIData() + } else { + t.Errorf("Invalid Typeof API Client %#v!", client) + } +} + +func TestGetUrl(t *testing.T) { + apiUrl := GetUrl("events", nil) + + _, err := url.Parse(apiUrl) + if err != nil { + t.Errorf("Invalid URL %#v\n", apiUrl) + } +} + +func TestGetAPIData(t *testing.T) { + data := GetAPIData("events", nil) + + body, err := json.Marshal(data) + if err != nil { + t.Errorf("Invalid JSON Format %#v\n", body) + } + + dummy_resp := &http.Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 0, + Body: ioutil.NopCloser(bytes.NewReader(body)), + } + + var dummy_events []Event + httpResponseToStruct(dummy_resp, &dummy_events) + if len(dummy_events) != 3 { + t.Error("Invalid event length!") + } + + for _, event := range dummy_events { + valid := reflect.TypeOf(Event{}) + actual := reflect.TypeOf(event) + if actual != valid { + t.Errorf("Invalid event type of %v! (should be %v).", actual, valid) + } + } +} diff --git a/const.go b/const.go index 3a99c59..d21865b 100644 --- a/const.go +++ b/const.go @@ -1,6 +1,8 @@ package ctftime const ( - BUFSIZE = 1024 - URL_PREFIX = "https://ctftime.org/api/v1" // API Endpoint + LIMIT = 3 + BUFSIZE = 1024 + API_ENDPOINT = "https://ctftime.org/api/v1" + test_dir = "./test_data/" ) diff --git a/event.go b/event.go index f8f4805..9a7f4fb 100644 --- a/event.go +++ b/event.go @@ -1,12 +1,8 @@ package ctftime -import ( - "fmt" - "log" - "net/http" - "strconv" - "time" -) +import "time" + +/* Struct */ type Event struct { Organizers []Organizer `json:"organizers"` @@ -41,25 +37,3 @@ type Duration struct { Hours int `json:"hours"` Days int `json:"days"` } - -const ( - LIMIT = 3 -) - -func BuildUrl() string { - now := time.Now().Unix() - url := URL_PREFIX + "/events/?limit=" + fmt.Sprintf("%d", LIMIT) + "&start=" + strconv.FormatInt(now, 10) - return url -} - -func GetAPIData() []Event { - var events []Event - url := BuildUrl() - response, err := http.Get(url) - if err != nil { - log.Fatal(err) - } - HttpResponseToStruct(response, &events) - - return events -} diff --git a/event_test.go b/event_test.go deleted file mode 100644 index ced121b..0000000 --- a/event_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package ctftime - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "reflect" - "testing" -) - -func TestDecodeGetAPIData(t *testing.T) { - result := GetAPIData() - body, _ := json.Marshal(result) - dummy_resp := &http.Response{ - StatusCode: 200, - ProtoMajor: 1, - ProtoMinor: 0, - Body: ioutil.NopCloser(bytes.NewReader(body)), - } - var api_data []Event - HttpResponseToStruct(dummy_resp, &api_data) - - if len(api_data) != 3 { - t.Error("Invalid event length!") - } - - for _, event := range api_data { - if reflect.TypeOf(Event{}) != reflect.TypeOf(event) { - t.Errorf("Invalid event type of %v! (should be %v).", reflect.TypeOf(event), reflect.TypeOf(&Event{})) - } - } -} diff --git a/events.go b/events.go new file mode 100644 index 0000000..b8aa8c2 --- /dev/null +++ b/events.go @@ -0,0 +1,49 @@ +package ctftime + +import ( + "fmt" + "log" + "net/http" + "strconv" + "time" +) + +type eventsAPIClient struct { + Ctx apiContext +} + +func newEventsAPIClient(ctx apiContext) apiClient { + return &eventsAPIClient{ + Ctx: ctx, + } +} + +func (client *eventsAPIClient) GetUrl() string { + now := time.Now().Unix() + + req, err := http.NewRequest("GET", API_ENDPOINT+"/events/", nil) + if err != nil { + log.Fatal(err) + } + + q := req.URL.Query() + q.Add("limit", fmt.Sprintf("%d", LIMIT)) + q.Add("start", strconv.FormatInt(now, 10)) + req.URL.RawQuery = q.Encode() + + return req.URL.String() +} + +func (client *eventsAPIClient) GetAPIData() interface{} { + url := client.GetUrl() + + resp, err := http.Get(url) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + var events []Event + httpResponseToStruct(resp, &events) + return events +} diff --git a/events_test.go b/events_test.go new file mode 100644 index 0000000..8f5d161 --- /dev/null +++ b/events_test.go @@ -0,0 +1,43 @@ +package ctftime + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "reflect" + "testing" +) + +func TestGetEventsData(t *testing.T) { + client := newAPIClient("events", nil) + + result := client.GetAPIData() + + body, err := json.Marshal(result) + if err != nil { + log.Fatal(err) + } + + dummy_resp := &http.Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 0, + Body: ioutil.NopCloser(bytes.NewReader(body)), + } + + var dummy_events []Event + httpResponseToStruct(dummy_resp, &dummy_events) + if len(dummy_events) != 3 { + t.Error("Invalid event length!") + } + + for _, event := range dummy_events { + valid := reflect.TypeOf(Event{}) + actual := reflect.TypeOf(event) + if actual != valid { + t.Errorf("Invalid event type of %v! (should be %v).", actual, valid) + } + } +} diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..f53aac1 --- /dev/null +++ b/glide.lock @@ -0,0 +1,4 @@ +hash: 24efb7e7374838eb3bda334c8ea67d00818b0ff3575c5cad5b7387c589e9f456 +updated: 2017-08-24T20:34:05.981794375+09:00 +imports: [] +testImports: [] diff --git a/util.go b/util.go index a4aedba..369333b 100644 --- a/util.go +++ b/util.go @@ -5,9 +5,25 @@ import ( "io/ioutil" "log" "net/http" + "path" ) -func HttpResponseToStruct(r *http.Response, v interface{}) { +/* Test */ + +func getTestData(fname string) []byte { + fpath := path.Join(test_dir, fname) + + data, err := ioutil.ReadFile(fpath) + if err != nil { + log.Fatal(err) + } + + return data +} + +/* HTTP */ + +func httpResponseToStruct(r *http.Response, v interface{}) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { diff --git a/util_test.go b/util_test.go index b8b90d1..eab3f5f 100644 --- a/util_test.go +++ b/util_test.go @@ -5,15 +5,30 @@ import ( "io/ioutil" "log" "net/http" + "path" "testing" ) -func TestDecodeJsonResponse(t *testing.T) { - buf, err := ioutil.ReadFile("./test_data/event_1.json") +const ( + testFile = "event_1.json" +) + +func TestGetTestData(t *testing.T) { + buf := getTestData(testFile) + + data, err := ioutil.ReadFile(path.Join(test_dir, testFile)) if err != nil { log.Fatal(err) } + if string(buf) != string(data) { + t.Errorf("Data doesn't match %v != %v\n", buf, data) + } +} + +func TestDecodeJsonResponse(t *testing.T) { + buf := getTestData("event_1.json") + // Create HTTP Response // 200 OK HTTP/1.0 // @@ -27,5 +42,5 @@ func TestDecodeJsonResponse(t *testing.T) { } var events []interface{} - HttpResponseToStruct(response, &events) + httpResponseToStruct(response, &events) }