commit 83276ce8b097443e137c487ea41c9f83cf7cfe9e Author: simon987 Date: Sat Jan 12 16:18:14 2019 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/api/error.go b/api/error.go new file mode 100644 index 0000000..4e77a36 --- /dev/null +++ b/api/error.go @@ -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) + } +} diff --git a/api/helper.go b/api/helper.go new file mode 100644 index 0000000..3d393e2 --- /dev/null +++ b/api/helper.go @@ -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 +} diff --git a/api/log.go b/api/log.go new file mode 100644 index 0000000..5d5a93d --- /dev/null +++ b/api/log.go @@ -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) +} + diff --git a/api/main.go b/api/main.go new file mode 100644 index 0000000..2a1efea --- /dev/null +++ b/api/main.go @@ -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) + } +} \ No newline at end of file diff --git a/api/project.go b/api/project.go new file mode 100644 index 0000000..b09a33e --- /dev/null +++ b/api/project.go @@ -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) + } +} \ No newline at end of file diff --git a/api/task.go b/api/task.go new file mode 100644 index 0000000..d9c5a40 --- /dev/null +++ b/api/task.go @@ -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) { + + +} \ No newline at end of file diff --git a/api/worker.go b/api/worker.go new file mode 100644 index 0000000..b036c6e --- /dev/null +++ b/api/worker.go @@ -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 +} diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..4434d24 --- /dev/null +++ b/config.yml @@ -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" diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..6f4134c --- /dev/null +++ b/config/config.go @@ -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") +} diff --git a/main/main.go b/main/main.go new file mode 100644 index 0000000..021dd17 --- /dev/null +++ b/main/main.go @@ -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() +} diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..5b1648e --- /dev/null +++ b/schema.sql @@ -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' +); + + diff --git a/storage/database.go b/storage/database.go new file mode 100644 index 0000000..7ddb629 --- /dev/null +++ b/storage/database.go @@ -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) + +} \ No newline at end of file diff --git a/storage/error.go b/storage/error.go new file mode 100644 index 0000000..3a31bc5 --- /dev/null +++ b/storage/error.go @@ -0,0 +1,7 @@ +package storage + +func handleErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/storage/project.go b/storage/project.go new file mode 100644 index 0000000..16dfd0d --- /dev/null +++ b/storage/project.go @@ -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 +} diff --git a/storage/task.go b/storage/task.go new file mode 100644 index 0000000..d468628 --- /dev/null +++ b/storage/task.go @@ -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 +} diff --git a/storage/worker.go b/storage/worker.go new file mode 100644 index 0000000..87777a2 --- /dev/null +++ b/storage/worker.go @@ -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 +} + diff --git a/test/api_log_test.go b/test/api_log_test.go new file mode 100644 index 0000000..d613da0 --- /dev/null +++ b/test/api_log_test.go @@ -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() + } +} diff --git a/test/api_project_test.go b/test/api_project_test.go new file mode 100644 index 0000000..2232a14 --- /dev/null +++ b/test/api_project_test.go @@ -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 +} \ No newline at end of file diff --git a/test/api_task_test.go b/test/api_task_test.go new file mode 100644 index 0000000..99b1a29 --- /dev/null +++ b/test/api_task_test.go @@ -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 +} diff --git a/test/api_worker_test.go b/test/api_worker_test.go new file mode 100644 index 0000000..628664b --- /dev/null +++ b/test/api_worker_test.go @@ -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 +} diff --git a/test/common.go b/test/common.go new file mode 100644 index 0000000..8870c55 --- /dev/null +++ b/test/common.go @@ -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 +} \ No newline at end of file diff --git a/test/config.yml b/test/config.yml new file mode 100644 index 0000000..c174348 --- /dev/null +++ b/test/config.yml @@ -0,0 +1,5 @@ +server: + address: "127.0.0.1:5001" + +database: + conn_str : "user=task_tracker dbname=task_tracker_test sslmode=disable" diff --git a/test/main_test.go b/test/main_test.go new file mode 100644 index 0000000..27da4b7 --- /dev/null +++ b/test/main_test.go @@ -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() +} diff --git a/test/schema.sql b/test/schema.sql new file mode 120000 index 0000000..1450fbb --- /dev/null +++ b/test/schema.sql @@ -0,0 +1 @@ +../schema.sql \ No newline at end of file