Initial commit

This commit is contained in:
simon987 2019-01-12 16:18:14 -05:00
commit 83276ce8b0
25 changed files with 1367 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea/

25
api/error.go Normal file
View File

@ -0,0 +1,25 @@
package api
import (
"github.com/Sirupsen/logrus"
"runtime/debug"
)
type ErrorResponse struct {
Message string `json:"message"`
StackTrace string `json:"stack_trace"`
}
func handleErr(err error, r *Request) {
if err != nil {
logrus.Error(err.Error())
//debug.PrintStack()
r.Json(ErrorResponse{
Message: err.Error(),
StackTrace: string(debug.Stack()),
}, 500)
}
}

48
api/helper.go Normal file
View File

@ -0,0 +1,48 @@
package api
import (
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
"github.com/valyala/fasthttp"
)
type Request struct {
Ctx *fasthttp.RequestCtx
}
func (r *Request) OkJson(object interface{}) {
resp, err := json.Marshal(object)
handleErr(err, r)
r.Ctx.Response.Header.Set("Content-Type", "application/json")
_, err = r.Ctx.Write(resp)
handleErr(err, r)
}
func (r *Request) Json(object interface{}, code int) {
resp, err := json.Marshal(object)
if err != nil {
fmt.Fprint(r.Ctx,"Error during json encoding of error")
logrus.Error("Error during json encoding of error")
}
r.Ctx.Response.SetStatusCode(code)
r.Ctx.Response.Header.Set("Content-Type", "application/json")
_, err = r.Ctx.Write(resp)
if err != nil {
panic(err) //todo handle differently
}
}
func (r *Request) GetJson(x interface{}) bool {
err := json.Unmarshal(r.Ctx.Request.Body(), x)
handleErr(err, r)
return err == nil
}

91
api/log.go Normal file
View File

@ -0,0 +1,91 @@
package api
import (
"errors"
"github.com/Sirupsen/logrus"
"github.com/valyala/fasthttp"
"time"
)
type RequestHandler func(*Request)
type LogEntry struct {
Scope string `json:"scope"`
Message string `json:"Message"`
TimeStamp int64 `json:"timestamp"`
}
func (e *LogEntry) Time() time.Time {
t := time.Unix(e.TimeStamp, 0)
return t
}
func LogRequest(h RequestHandler) fasthttp.RequestHandler {
return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) {
logrus.WithFields(logrus.Fields{
"path": string(ctx.Path()),
}).Info(string(ctx.Method()))
h(&Request{Ctx: ctx})
})
}
func SetupLogger() {
logrus.SetLevel(logrus.TraceLevel) //todo: from conf
}
func parseLogEntry(r *Request) *LogEntry {
entry := LogEntry{}
if r.GetJson(&entry) {
if len(entry.Message) == 0 {
handleErr(errors.New("invalid message"), r)
} else if len(entry.Scope) == 0 {
handleErr(errors.New("invalid scope"), r)
} else if entry.TimeStamp <= 0 {
handleErr(errors.New("invalid timestamp"), r)
}
}
return &entry
}
func LogTrace(r *Request) {
entry := parseLogEntry(r)
logrus.WithFields(logrus.Fields{
"scope": entry.Scope,
}).WithTime(entry.Time()).Trace(entry.Message)
}
func LogInfo(r *Request) {
entry := parseLogEntry(r)
logrus.WithFields(logrus.Fields{
"scope": entry.Scope,
}).WithTime(entry.Time()).Info(entry.Message)
}
func LogWarn(r *Request) {
entry := parseLogEntry(r)
logrus.WithFields(logrus.Fields{
"scope": entry.Scope,
}).WithTime(entry.Time()).Warn(entry.Message)
}
func LogError(r *Request) {
entry := parseLogEntry(r)
logrus.WithFields(logrus.Fields{
"scope": entry.Scope,
}).WithTime(entry.Time()).Error(entry.Message)
}

71
api/main.go Normal file
View File

@ -0,0 +1,71 @@
package api
import (
"github.com/Sirupsen/logrus"
"github.com/buaazp/fasthttprouter"
"github.com/valyala/fasthttp"
"src/task_tracker/config"
"src/task_tracker/storage"
)
type WebAPI struct {
server *fasthttp.Server
router *fasthttprouter.Router
Database *storage.Database
}
type Info struct {
Name string `json:"name"`
Version string `json:"version"`
}
var info = Info {
Name: "task_tracker",
Version: "1.0",
}
func Index(r *Request) {
r.OkJson(info)
}
func New() *WebAPI {
SetupLogger()
api := new(WebAPI)
api.router = &fasthttprouter.Router{}
api.server = &fasthttp.Server{
Handler: api.router.Handler,
Name: info.Name,
}
api.router.GET("/", LogRequest(Index))
api.router.POST("/log/trace", LogRequest(LogTrace))
api.router.POST("/log/info", LogRequest(LogInfo))
api.router.POST("/log/warn", LogRequest(LogWarn))
api.router.POST("/log/error", LogRequest(LogError))
api.router.POST("/worker/create", LogRequest(api.WorkerCreate))
api.router.GET("/worker/get/:id", LogRequest(api.WorkerGet))
api.router.POST("/project/create", LogRequest(api.ProjectCreate))
api.router.GET("/project/get/:id", LogRequest(api.ProjectGet))
api.router.POST("/task/create", LogRequest(api.TaskCreate))
api.router.GET("/task/get/", LogRequest(api.TaskGet))
return api
}
func (api *WebAPI) Run() {
logrus.Infof("Started web api at address %s", config.Cfg.ServerAddr)
err := api.server.ListenAndServe(config.Cfg.ServerAddr)
if err != nil {
logrus.Fatalf("Error in ListenAndServe: %s", err)
}
}

92
api/project.go Normal file
View File

@ -0,0 +1,92 @@
package api
import (
"github.com/Sirupsen/logrus"
"src/task_tracker/storage"
"strconv"
)
type CreateProjectRequest struct {
Name string `json:"name"`
GitUrl string `json:"git_url"`
Version string `json:"version"`
}
type CreateProjectResponse struct {
Ok bool `json:"ok"`
Id int64 `json:"id,omitempty"`
Message string `json:"message,omitempty"`
}
type GetProjectResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
Project *storage.Project `json:"project,omitempty"`
}
func (api *WebAPI) ProjectCreate(r *Request) {
createReq := &CreateProjectRequest{}
if r.GetJson(createReq) {
project := &storage.Project{
Name: createReq.Name,
Version: createReq.Version,
GitUrl: createReq.GitUrl,
}
if isValidProject(project) {
id, err := api.Database.SaveProject(project)
if err != nil {
r.Json(CreateProjectResponse{
Ok: false,
Message:err.Error(),
}, 500)
} else {
r.OkJson(CreateProjectResponse{
Ok: true,
Id: id,
})
}
} else {
logrus.WithFields(logrus.Fields{
"project": project,
}).Warn("Invalid project")
r.Json(CreateProjectResponse{
Ok: false,
Message: "Invalid project",
}, 400)
}
}
}
func isValidProject(project *storage.Project) bool {
if len(project.Name) <= 0 {
return false
}
return true
}
func (api *WebAPI) ProjectGet(r *Request) {
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
handleErr(err, r)
project := api.Database.GetProject(id)
if project != nil {
r.OkJson(GetProjectResponse{
Ok: true,
Project:project,
})
} else {
r.Json(GetProjectResponse{
Ok: false,
Message: "Project not found",
}, 404)
}
}

73
api/task.go Normal file
View File

@ -0,0 +1,73 @@
package api
import (
"github.com/Sirupsen/logrus"
"src/task_tracker/storage"
)
type CreateTaskRequest struct {
Project int64 `json:"project"`
MaxRetries int64 `json:"max_retries"`
Recipe string `json:"recipe"`
}
type CreateTaskResponse struct {
Ok bool
Message string
}
func (api *WebAPI) TaskCreate(r *Request) {
var createReq CreateTaskRequest
if r.GetJson(&createReq) {
task := &storage.Task{
Project:createReq.Project,
MaxRetries: createReq.MaxRetries,
Recipe:createReq.Recipe,
}
if isTaskValid(task) {
err := api.Database.SaveTask(task)
if err != nil {
r.Json(CreateTaskResponse{
Ok: false,
Message: err.Error(), //todo: hide sensitive error?
}, 500)
} else {
r.OkJson(CreateTaskResponse{
Ok: true,
})
}
} else {
logrus.WithFields(logrus.Fields{
"task": task,
}).Warn("Invalid task")
r.Json(CreateTaskResponse{
Ok: false,
Message: "Invalid task",
}, 400)
}
}
}
func isTaskValid(task *storage.Task) bool {
if task.MaxRetries < 0 {
return false
}
if task.Project <= 0 {
return false
}
if len(task.Recipe) <= 0 {
return false
}
return true
}
func (api *WebAPI) TaskGet(r *Request) {
}

105
api/worker.go Normal file
View File

@ -0,0 +1,105 @@
package api
import (
"github.com/Sirupsen/logrus"
"github.com/google/uuid"
"src/task_tracker/storage"
"time"
)
type CreateWorkerRequest struct {
}
type CreateWorkerResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
WorkerId string `json:"id,omitempty"`
}
type GetWorkerResponse struct {
Ok bool `json:"ok"`
Message string `json:"message,omitempty"`
Worker *storage.Worker `json:"worker,omitempty"`
}
func (api *WebAPI) WorkerCreate(r *Request) {
workerReq := &CreateWorkerRequest{}
if r.GetJson(workerReq) {
identity := getIdentity(r)
if canCreateWorker(r, workerReq, identity) {
id, err := api.workerCreate(workerReq, getIdentity(r))
if err != nil {
handleErr(err, r)
} else {
r.OkJson(CreateWorkerResponse{
Ok: true,
WorkerId: id.String(),
})
}
} else {
r.Json(CreateWorkerResponse{
Ok: false,
Message: "You are now allowed to create a worker",
}, 403)
}
}
}
func (api *WebAPI) WorkerGet(r *Request) {
id, err := uuid.Parse(r.Ctx.UserValue("id").(string))
if err != nil {
logrus.WithFields(logrus.Fields{
"id": id,
}).Warn("Invalid UUID")
r.Json(GetWorkerResponse{
Ok: false,
Message:err.Error(),
}, 400)
return
}
worker := api.Database.GetWorker(id)
if worker != nil {
r.OkJson(GetWorkerResponse{
Ok: true,
Worker:worker,
})
} else {
r.Json(GetWorkerResponse{
Ok: false,
Message:"Worker not found",
}, 404)
}
}
func (api *WebAPI) workerCreate(request *CreateWorkerRequest, identity *storage.Identity) (uuid.UUID, error) {
worker := storage.Worker{
Id: uuid.New(),
Created: time.Now().Unix(),
Identity: identity,
}
api.Database.SaveWorker(&worker)
return worker.Id, nil
}
func canCreateWorker(r *Request, cwr *CreateWorkerRequest, identity *storage.Identity) bool {
return true
}
func getIdentity(r *Request) *storage.Identity {
identity := storage.Identity{
RemoteAddr: r.Ctx.RemoteAddr().String(),
}
return &identity
}

7
config.yml Normal file
View File

@ -0,0 +1,7 @@
server:
address: "127.0.0.1:5000"
test_address: "127.0.0.1:5001"
database:
conn_str : "user=task_tracker dbname=task_tracker sslmode=disable"
test_conn_str : "user=task_tracker dbname=task_tracker_test sslmode=disable"

24
config/config.go Normal file
View File

@ -0,0 +1,24 @@
package config
import (
"github.com/spf13/viper"
)
var Cfg struct {
ServerAddr string
DbConnStr string
}
func SetupConfig() {
viper.AddConfigPath(".")
viper.SetConfigName("")
err := viper.ReadInConfig()
if err != nil {
panic(err)
}
Cfg.ServerAddr = viper.GetString("server.address")
Cfg.DbConnStr = viper.GetString("database.conn_str")
}

24
main/main.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"src/task_tracker/api"
"src/task_tracker/config"
"src/task_tracker/storage"
)
func tmpDebugSetup() {
db := storage.Database{}
db.Reset()
}
func main() {
config.SetupConfig()
tmpDebugSetup()
webApi := api.New()
webApi.Run()
}

43
schema.sql Normal file
View File

@ -0,0 +1,43 @@
DROP TABLE IF EXISTS workerIdentity, Worker, Project, Task;
DROP TYPE IF EXISTS Status;
CREATE TYPE status as ENUM (
'new',
'failed',
'closed'
);
CREATE TABLE workerIdentity
(
id SERIAL PRIMARY KEY,
remote_addr TEXT,
UNIQUE (remote_addr)
);
CREATE TABLE worker
(
id TEXT PRIMARY KEY,
created INTEGER,
identity INTEGER REFERENCES workerIdentity(id)
);
CREATE TABLE project
(
id SERIAL PRIMARY KEY,
name TEXT UNIQUE,
git_url TEXT,
version TEXT
);
CREATE TABLE task
(
id TEXT PRIMARY KEY,
project INTEGER REFERENCES project (id),
assignee TEXT REFERENCES worker (id),
retries INTEGER DEFAULT 0,
max_retries INTEGER,
status Status DEFAULT 'new'
);

53
storage/database.go Normal file
View File

@ -0,0 +1,53 @@
package storage
import (
"database/sql"
"fmt"
"github.com/Sirupsen/logrus"
_ "github.com/lib/pq"
"io/ioutil"
"os"
"src/task_tracker/config"
)
type Database struct {
}
func (database *Database) Reset() {
file, err := os.Open("./schema.sql")
handleErr(err)
buffer, err := ioutil.ReadAll(file)
handleErr(err)
db := database.getDB()
_, err = db.Exec(string(buffer))
handleErr(err)
db.Close()
file.Close()
logrus.Info("Database has been reset")
}
func (database *Database) getDB () *sql.DB {
db, err := sql.Open("postgres", config.Cfg.DbConnStr)
if err != nil {
logrus.Fatal(err)
}
return db
}
func (database *Database) Test() {
db := database.getDB()
rows, err := db.Query("SELECT name FROM Task")
if err != nil {
logrus.Fatal(err)
}
fmt.Println(rows)
}

7
storage/error.go Normal file
View File

@ -0,0 +1,7 @@
package storage
func handleErr(err error) {
if err != nil {
panic(err)
}
}

77
storage/project.go Normal file
View File

@ -0,0 +1,77 @@
package storage
import (
"database/sql"
"github.com/Sirupsen/logrus"
)
type Project struct {
Id int64 `json:"id"`
Name string `json:"name"`
GitUrl string `json:"git_url"`
Version string `json:"version"`
}
func (database *Database) SaveProject(project *Project) (int64, error) {
db := database.getDB()
id, projectErr := saveProject(project, db)
err := db.Close()
handleErr(err)
return id, projectErr
}
func saveProject(project *Project, db *sql.DB) (int64, error) {
row := db.QueryRow("INSERT INTO project (name, git_url, version) VALUES ($1,$2,$3) RETURNING id",
project.Name, project.GitUrl, project.Version)
var id int64
err := row.Scan(&id)
if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"project": project,
}).Warn("Database.saveProject INSERT project ERROR")
return -1, err
}
logrus.WithFields(logrus.Fields{
"id": id,
"project": project,
}).Trace("Database.saveProject INSERT project")
return id, nil
}
func (database *Database) GetProject(id int64) *Project {
db := database.getDB()
project := getProject(id, db)
err := db.Close()
handleErr(err)
return project
}
func getProject(id int64, db *sql.DB) *Project {
project := &Project{}
row := db.QueryRow("SELECT id, name, git_url, version FROM project WHERE id=$1",
id)
err := row.Scan(&project.Id, &project.Name, &project.GitUrl, &project.Version)
if err != nil {
logrus.WithFields(logrus.Fields{
"id": id,
}).Warn("Database.getProject SELECT project NOT FOUND")
return nil
}
logrus.WithFields(logrus.Fields{
"id": id,
"project": project,
}).Trace("Database.saveProject SELECT project")
return project
}

50
storage/task.go Normal file
View File

@ -0,0 +1,50 @@
package storage
import (
"database/sql"
"github.com/Sirupsen/logrus"
"github.com/google/uuid"
)
type Task struct {
Id int64
Project int64
Assignee uuid.UUID
Retries int64
MaxRetries int64
Status string
Recipe string
}
func (database *Database) SaveTask(task *Task) error {
db := database.getDB()
taskErr := saveTask(task, db)
err := db.Close()
handleErr(err)
return taskErr
}
func saveTask(task *Task, db *sql.DB) error {
res, err := db.Exec("INSERT INTO task (project, max_retries, recipe) "+
"VALUES ($1,$2,$3)",
task.Project, task.MaxRetries, task.Recipe)
if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"task": task,
}).Warn("Database.saveTask INSERT task ERROR")
return err
}
rowsAffected, err := res.RowsAffected()
handleErr(err)
logrus.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
"task": task,
}).Trace("Database.saveTask INSERT task")
return nil
}

116
storage/worker.go Normal file
View File

@ -0,0 +1,116 @@
package storage
import (
"database/sql"
"errors"
"github.com/Sirupsen/logrus"
"github.com/google/uuid"
)
type Identity struct {
RemoteAddr string
}
type Worker struct {
Id uuid.UUID `json:"id"`
Created int64 `json:"created"`
Identity *Identity `json:"identity"`
}
func (database *Database) SaveWorker(worker *Worker) {
db := database.getDB()
saveWorker(worker, db)
err := db.Close()
handleErr(err)
}
func (database *Database) GetWorker(id uuid.UUID) *Worker {
db := database.getDB()
worker := getWorker(id, db)
err := db.Close()
handleErr(err)
return worker
}
func saveWorker(worker *Worker, db *sql.DB) {
identityId := getOrCreateIdentity(worker.Identity, db)
res, err := db.Exec("INSERT INTO worker (id, created, identity) VALUES ($1,$2,$3)",
worker.Id, worker.Created, identityId)
handleErr(err)
var rowsAffected, _ = res.RowsAffected()
logrus.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
}).Trace("Database.saveWorker INSERT worker")
}
func getWorker(id uuid.UUID, db *sql.DB) *Worker {
worker := &Worker{}
var identityId int64
row := db.QueryRow("SELECT id, created, identity FROM worker WHERE id=$1", id)
err := row.Scan(&worker.Id, &worker.Created, &identityId)
if err != nil {
logrus.WithFields(logrus.Fields{
"id": id,
}).Warn("Database.getWorker SELECT worker NOT FOUND")
return nil
}
worker.Identity, err = getIdentity(identityId, db)
handleErr(err)
logrus.WithFields(logrus.Fields{
"worker": worker,
}).Trace("Database.getWorker SELECT worker")
return worker
}
func getIdentity(id int64, db *sql.DB) (*Identity, error) {
identity := &Identity{}
row := db.QueryRow("SELECT (remote_addr) FROM workeridentity WHERE id=$1", id)
err := row.Scan(&identity.RemoteAddr)
if err != nil {
return nil, errors.New("identity not found")
}
logrus.WithFields(logrus.Fields{
"identity": identity,
}).Trace("Database.getIdentity SELECT workerIdentity")
return identity, nil
}
func getOrCreateIdentity(identity *Identity, db *sql.DB) int64 {
res, err := db.Exec("INSERT INTO workeridentity (remote_addr) VALUES ($1) ON CONFLICT DO NOTHING",
identity.RemoteAddr)
handleErr(err)
rowsAffected, err := res.RowsAffected()
logrus.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
}).Trace("Database.saveWorker INSERT workerIdentity")
row := db.QueryRow("SELECT (id) FROM workeridentity WHERE remote_addr=$1", identity.RemoteAddr)
var rowId int64
err = row.Scan(&rowId)
handleErr(err)
logrus.WithFields(logrus.Fields{
"rowId": rowId,
}).Trace("Database.saveWorker SELECT workerIdentity")
return rowId
}

110
test/api_log_test.go Normal file
View File

@ -0,0 +1,110 @@
package test
import (
"src/task_tracker/api"
"testing"
"time"
)
func TestTraceValid(t *testing.T) {
r := Post("/log/trace", api.LogEntry{
Scope:"test",
Message:"This is a test message",
TimeStamp: time.Now().Unix(),
})
if r.StatusCode != 200 {
t.Fail()
}
}
func TestTraceInvalidScope(t *testing.T) {
r := Post("/log/trace", api.LogEntry{
Message:"this is a test message",
TimeStamp: time.Now().Unix(),
})
if r.StatusCode != 500 {
t.Fail()
}
r = Post("/log/trace", api.LogEntry{
Scope:"",
Message:"this is a test message",
TimeStamp: time.Now().Unix(),
})
if r.StatusCode != 500 {
t.Fail()
}
if GenericJson(r.Body)["message"] != "invalid scope" {
t.Fail()
}
}
func TestTraceInvalidMessage(t *testing.T) {
r := Post("/log/trace", api.LogEntry{
Scope:"test",
Message:"",
TimeStamp: time.Now().Unix(),
})
if r.StatusCode != 500 {
t.Fail()
}
if GenericJson(r.Body)["message"] != "invalid message" {
t.Fail()
}
}
func TestTraceInvalidTime(t *testing.T) {
r := Post("/log/trace", api.LogEntry{
Scope: "test",
Message:"test",
})
if r.StatusCode != 500 {
t.Fail()
}
if GenericJson(r.Body)["message"] != "invalid timestamp" {
t.Fail()
}
}
func TestWarnValid(t *testing.T) {
r := Post("/log/warn", api.LogEntry{
Scope: "test",
Message:"test",
TimeStamp:time.Now().Unix(),
})
if r.StatusCode != 200 {
t.Fail()
}
}
func TestInfoValid(t *testing.T) {
r := Post("/log/info", api.LogEntry{
Scope: "test",
Message:"test",
TimeStamp:time.Now().Unix(),
})
if r.StatusCode != 200 {
t.Fail()
}
}
func TestErrorValid(t *testing.T) {
r := Post("/log/error", api.LogEntry{
Scope: "test",
Message:"test",
TimeStamp:time.Now().Unix(),
})
if r.StatusCode != 200 {
t.Fail()
}
}

114
test/api_project_test.go Normal file
View File

@ -0,0 +1,114 @@
package test
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"src/task_tracker/api"
"testing"
)
func TestCreateGetProject(t *testing.T) {
resp := createProject(api.CreateProjectRequest{
Name: "Test name",
GitUrl: "http://github.com/test/test",
Version: "Test Version",
})
id := resp.Id
if id == 0 {
t.Fail()
}
if resp.Ok != true {
t.Fail()
}
getResp, _ := getProject(id)
if getResp.Project.Id != id {
t.Fail()
}
if getResp.Project.Name != "Test name" {
t.Fail()
}
if getResp.Project.Version != "Test Version" {
t.Fail()
}
if getResp.Project.GitUrl != "http://github.com/test/test" {
t.Fail()
}
}
func TestCreateProjectInvalid(t *testing.T) {
resp := createProject(api.CreateProjectRequest{
})
if resp.Ok != false {
t.Fail()
}
}
func TestCreateDuplicateProject(t *testing.T) {
createProject(api.CreateProjectRequest{
Name: "duplicate name",
})
resp := createProject(api.CreateProjectRequest{
Name: "duplicate name",
})
if resp.Ok != false {
t.Fail()
}
if len(resp.Message) <= 0 {
t.Fail()
}
}
func TestGetProjectNotFound(t *testing.T) {
getResp, r := getProject(12345)
if getResp.Ok != false {
t.Fail()
}
if len(getResp.Message) <= 0 {
t.Fail()
}
if r.StatusCode != 404 {
t.Fail()
}
}
func createProject(req api.CreateProjectRequest) *api.CreateProjectResponse {
r := Post("/project/create", req)
var resp api.CreateProjectResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return &resp
}
func getProject(id int64) (*api.GetProjectResponse, *http.Response) {
r := Get(fmt.Sprintf("/project/get/%d", id))
var getResp api.GetProjectResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &getResp)
handleErr(err)
return &getResp, r
}

74
test/api_task_test.go Normal file
View File

@ -0,0 +1,74 @@
package test
import (
"encoding/json"
"io/ioutil"
"src/task_tracker/api"
"testing"
)
func TestCreateTaskValid(t *testing.T) {
//Make sure there is always a project for id:1
createProject(api.CreateProjectRequest{
Name: "Some Test name",
Version: "Test Version",
GitUrl: "http://github.com/test/test",
})
resp := createTask(api.CreateTaskRequest{
Project:1,
Recipe: "{}",
MaxRetries:3,
})
if resp.Ok != true {
t.Fail()
}
}
func TestCreateTaskInvalidProject(t *testing.T) {
resp := createTask(api.CreateTaskRequest{
Project:123456,
Recipe: "{}",
MaxRetries:3,
})
if resp.Ok != false {
t.Fail()
}
if len(resp.Message) <= 0 {
t.Fail()
}
}
func TestCreateTaskInvalidRetries(t *testing.T) {
resp := createTask(api.CreateTaskRequest{
Project:1,
MaxRetries:-1,
})
if resp.Ok != false {
t.Fail()
}
if len(resp.Message) <= 0 {
t.Fail()
}
}
func createTask(request api.CreateTaskRequest) *api.CreateTaskResponse {
r := Post("/task/create", request)
var resp api.CreateTaskResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return &resp
}

83
test/api_worker_test.go Normal file
View File

@ -0,0 +1,83 @@
package test
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"src/task_tracker/api"
"testing"
)
func TestCreateGetWorker(t *testing.T) {
resp, r := createWorker(api.CreateWorkerRequest{})
if r.StatusCode != 200 {
t.Fail()
}
if resp.Ok != true {
t.Fail()
}
getResp, r := getWorker(resp.WorkerId)
if r.StatusCode != 200 {
t.Fail()
}
if resp.WorkerId != getResp.Worker.Id.String() {
t.Fail()
}
}
func TestGetWorkerNotFound(t *testing.T) {
resp, r := getWorker("8bfc0ccd-d5ce-4dc5-a235-3a7ae760d9c6")
if r.StatusCode != 404 {
t.Fail()
}
if resp.Ok != false {
t.Fail()
}
}
func TestGetWorkerInvalid(t *testing.T) {
resp, r := getWorker("invalid-uuid")
if r.StatusCode != 400 {
t.Fail()
}
if resp.Ok != false {
t.Fail()
}
if len(resp.Message) <= 0 {
t.Fail()
}
}
func createWorker(req api.CreateWorkerRequest) (*api.CreateWorkerResponse, *http.Response) {
r := Post("/worker/create", req)
var resp *api.CreateWorkerResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return resp, r
}
func getWorker(id string) (*api.GetWorkerResponse, *http.Response) {
r := Get(fmt.Sprintf("/worker/get/%s", id))
var resp *api.GetWorkerResponse
data, _ := ioutil.ReadAll(r.Body)
err := json.Unmarshal(data, &resp)
handleErr(err)
return resp, r
}

53
test/common.go Normal file
View File

@ -0,0 +1,53 @@
package test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"src/task_tracker/config"
)
func Post(path string, x interface{}) *http.Response {
body, err := json.Marshal(x)
buf := bytes.NewBuffer(body)
r, err := http.Post("http://" + config.Cfg.ServerAddr + path, "application/json", buf)
handleErr(err)
return r
}
func Get(path string) *http.Response {
r, err := http.Get("http://" + config.Cfg.ServerAddr + path)
handleErr(err)
return r
}
func handleErr(err error) {
if err != nil {
panic(err)
}
}
func Print(body io.ReadCloser) {
rawBody, _ := ioutil.ReadAll(body)
fmt.Println(string(rawBody))
}
func GenericJson(body io.ReadCloser) map[string]interface{} {
var obj map[string]interface{}
data, _ := ioutil.ReadAll(body)
err := json.Unmarshal(data, &obj)
handleErr(err)
return obj
}

5
test/config.yml Normal file
View File

@ -0,0 +1,5 @@
server:
address: "127.0.0.1:5001"
database:
conn_str : "user=task_tracker dbname=task_tracker_test sslmode=disable"

20
test/main_test.go Normal file
View File

@ -0,0 +1,20 @@
package test
import (
"src/task_tracker/api"
"src/task_tracker/config"
"testing"
"time"
)
func TestMain(m *testing.M) {
config.SetupConfig()
testApi := api.New()
testApi.Database.Reset()
go testApi.Run()
time.Sleep(time.Millisecond * 100)
m.Run()
}

1
test/schema.sql Symbolic link
View File

@ -0,0 +1 @@
../schema.sql