package main import ( "encoding/json" "github.com/ansible-semaphore/semaphore/db" trans "github.com/snikch/goodman/transaction" "regexp" "strconv" "strings" ) // STATE // Runtime created objects we need to reference in test setups var testRunnerUser *db.User var userPathTestUser *db.User var userProject *db.Project var userKey *db.AccessKey var task *db.Task var schedule *db.Schedule var view *db.View // Runtime created simple ID values for some items we need to reference in other objects var repoID int var inventoryID int var environmentID int var templateID int var capabilities = map[string][]string{ "user": {}, "project": {"user"}, "repository": {"access_key"}, "inventory": {"repository"}, "environment": {"repository"}, "template": {"repository", "inventory", "environment", "view"}, "task": {"template"}, "schedule": {"template"}, "view": {}, } func capabilityWrapper(cap string) func(t *trans.Transaction) { return func(t *trans.Transaction) { addCapabilities([]string{cap}) } } func addCapabilities(caps []string) { dbConnect() defer store.Close() resolved := make([]string, 0) uid := getUUID() resolveCapability(caps, resolved, uid) } func resolveCapability(caps []string, resolved []string, uid string) { for _, v := range caps { //if cap has deps resolve them if val, ok := capabilities[v]; ok { resolveCapability(val, resolved, uid) } //skip if already resolved if _, exists := stringInSlice(v, resolved); exists { continue } //Add dep specific stuff switch v { case "schedule": schedule = addSchedule() case "view": view = addView() case "user": userPathTestUser = addUser() case "project": userProject = addProject() //allow the admin user (test executor) to manipulate the project addUserProjectRelation(userProject.ID, testRunnerUser.ID) addUserProjectRelation(userProject.ID, userPathTestUser.ID) case "access_key": userKey = addAccessKey(&userProject.ID) case "repository": pRepo, err := store.CreateRepository(db.Repository{ ProjectID: userProject.ID, GitURL: "git@github.com/ansible,semaphore/semaphore", GitBranch: "develop", SSHKeyID: userKey.ID, Name: "ITR-" + uid, }) printError(err) repoID = pRepo.ID case "inventory": res, err := store.CreateInventory(db.Inventory{ ProjectID: userProject.ID, Name: "ITI-" + uid, Type: "static", SSHKeyID: &userKey.ID, Inventory: "Test Inventory", }) printError(err) inventoryID = res.ID case "environment": pwd := "test-pass" env := "{}" res, err := store.CreateEnvironment(db.Environment{ ProjectID: userProject.ID, Name: "ITI-" + uid, JSON: "{}", Password: &pwd, ENV: &env, }) printError(err) environmentID = res.ID case "template": args := "[]" desc := "Hello, World!" res, err := store.CreateTemplate(db.Template{ ProjectID: userProject.ID, InventoryID: inventoryID, RepositoryID: repoID, EnvironmentID: &environmentID, Name: "Test-" + uid, Playbook: "test-playbook.yml", Arguments: &args, AllowOverrideArgsInTask: false, Description: &desc, ViewID: &view.ID, }) printError(err) templateID = res.ID case "task": task = addTask() default: panic("unknown capability " + v) } resolved = append(resolved, v) } } // HOOKS var skipTest = func(t *trans.Transaction) { t.Skip = true } // Contains all the substitutions for paths under test // The parameter example value in the api-doc should respond to the index+1 of the function in this slice // ie the project id, with example value 1, will be replaced by the return value of pathSubPatterns[0] var pathSubPatterns = []func() string{ func() string { return strconv.Itoa(userProject.ID) }, func() string { return strconv.Itoa(userPathTestUser.ID) }, func() string { return strconv.Itoa(userKey.ID) }, func() string { return strconv.Itoa(int(repoID)) }, func() string { return strconv.Itoa(int(inventoryID)) }, func() string { return strconv.Itoa(int(environmentID)) }, func() string { return strconv.Itoa(int(templateID)) }, func() string { return strconv.Itoa(task.ID) }, func() string { return strconv.Itoa(schedule.ID) }, func() string { return strconv.Itoa(view.ID) }, } // alterRequestPath with the above slice of functions func alterRequestPath(t *trans.Transaction) { pathArgs := strings.Split(t.FullPath, "/") exploded := make([]string, len(pathArgs)) copy(exploded, pathArgs) for k, v := range pathSubPatterns { pos, exists := stringInSlice(strconv.Itoa(k+1), exploded) if exists { pathArgs[pos] = v() } } t.FullPath = strings.Join(pathArgs, "/") t.Request.URI = t.FullPath } func alterRequestBody(t *trans.Transaction) { var request map[string]interface{} json.Unmarshal([]byte(t.Request.Body), &request) if userProject != nil { bodyFieldProcessor("project_id", userProject.ID, &request) } bodyFieldProcessor("json", "{}", &request) if userKey != nil { bodyFieldProcessor("ssh_key_id", userKey.ID, &request) } bodyFieldProcessor("environment_id", environmentID, &request) bodyFieldProcessor("inventory_id", inventoryID, &request) bodyFieldProcessor("repository_id", repoID, &request) bodyFieldProcessor("template_id", templateID, &request) if task != nil { bodyFieldProcessor("task_id", task.ID, &request) } if schedule != nil { bodyFieldProcessor("schedule_id", schedule.ID, &request) } if view != nil { bodyFieldProcessor("view_id", view.ID, &request) } // Inject object ID to body for PUT requests if strings.ToLower(t.Request.Method) == "put" { putRequestPathRE := regexp.MustCompile(`/api/(?:project/\d+/)?\w+/(\d+)/?$`) m := putRequestPathRE.FindStringSubmatch(t.FullPath) if len(m) > 0 { objectID, err := strconv.Atoi(m[1]) if err != nil { panic("Invalid object ID in PUT request " + t.FullPath) } request["id"] = objectID } else { panic("Unexpected PUT request " + t.FullPath) } } out, _ := json.Marshal(request) t.Request.Body = string(out) } func bodyFieldProcessor(id string, sub interface{}, request *map[string]interface{}) { if _, ok := (*request)[id]; ok { (*request)[id] = sub } }