From a981070cb540eac80c6d980a18123a2b960a226c Mon Sep 17 00:00:00 2001 From: AlexisPoyen <apoyen@grandlyon.com> Date: Wed, 10 Jun 2020 15:09:27 +0200 Subject: [PATCH] Feat : API for CandidateList --- internal/models/candidateList.go | 125 ++++++++++++++++++++++++++++ internal/models/models.go | 6 +- internal/rootmux/admin_test.go | 11 +++ internal/rootmux/capturer_test.go | 19 ++++- internal/rootmux/rootmux_test.go | 5 +- internal/rootmux/visualizer_test.go | 19 ++++- 6 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 internal/models/candidateList.go diff --git a/internal/models/candidateList.go b/internal/models/candidateList.go new file mode 100644 index 0000000..cedac8b --- /dev/null +++ b/internal/models/candidateList.go @@ -0,0 +1,125 @@ +package models + +import ( + "encoding/json" + "net/http" + "strconv" + "strings" + + "forge.grandlyon.com/apoyen/elections/internal/auth" +) + +func (d *DataHandler) handleCandidateList(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/api/CandidateList/")) + switch method := r.Method; method { + case "GET": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN", "CAPTURER", "VISUALIZER": + d.getCandidateList(w, r, id) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "POST": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + d.postCandidateList(w, r) + case "CAPTURER", "VISUALIZER": + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + + case "PUT": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + d.putCandidateList(w, r, id) + case "CAPTURER", "VISUALIZER": + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + case "DELETE": + switch auth.GetLoggedUserTechnical(w, r).Role { + case "ADMIN": + d.deleteCandidateList(w, r, id) + case "CAPTURER", "VISUALIZER": + http.Error(w, ErrorNotAuthorizeMethodOnRessource, http.StatusMethodNotAllowed) + default: + http.Error(w, ErrorRoleOfLoggedUser, http.StatusInternalServerError) + } + default: + http.Error(w, "method not allowed", 400) + } +} + +func (d *DataHandler) getCandidateList(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o CandidateList + if err := d.db.Preload("Candidates").Preload("Votes").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + json.NewEncoder(w).Encode(o) + } else { + var o []CandidateList + d.db.Preload("Candidates").Preload("Votes").Find(&o) + json.NewEncoder(w).Encode(o) + } +} + +func (d *DataHandler) postCandidateList(w http.ResponseWriter, r *http.Request) { + var o CandidateList + err := json.NewDecoder(r.Body).Decode(&o) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // Check that AreaID exist + var area Area + if err := d.db.First(&area, o.AreaID).Error; err != nil { + http.Error(w, ErrorParentNotFound, http.StatusInternalServerError) + return + } + + d.db.Create(&o) + d.db.Last(&o) + json.NewEncoder(w).Encode(o) + +} + +func (d *DataHandler) putCandidateList(w http.ResponseWriter, r *http.Request, id int) { + var o CandidateList + if err := d.db.Preload("Candidates").Preload("Votes").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + var candidateList CandidateList + err := json.NewDecoder(r.Body).Decode(&candidateList) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + o.Name = candidateList.Name + o.PartyID = candidateList.PartyID + o.RoundID = candidateList.RoundID + o.AreaID = candidateList.AreaID + d.db.Save(&o) + json.NewEncoder(w).Encode(o) + +} + +func (d *DataHandler) deleteCandidateList(w http.ResponseWriter, r *http.Request, id int) { + if id != 0 { + var o CandidateList + if err := d.db.Preload("Candidates").Preload("Votes").First(&o, id).Error; err != nil { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + return + } + + // TODO remove candidate in cascade + + d.db.Delete(&o) + } else { + http.Error(w, ErrorIDIsMissing, http.StatusNotFound) + } +} diff --git a/internal/models/models.go b/internal/models/models.go index 6f0f115..526d8c8 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -153,10 +153,10 @@ type CandidateList struct { CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` DeletedAt *time.Time `json:"-"` + Name string PartyID uint RoundID uint - Area Area `gorm:"foreignkey:AreaRefer"` - Name string + AreaID uint `gorm:"foreignkey:AreaRefer"` Candidates []Candidate Votes []Vote } @@ -236,6 +236,8 @@ func (d *DataHandler) ProcessAPI(w http.ResponseWriter, r *http.Request) { d.handleCapturerDeskRound(w, r) case "Party": d.handleParty(w, r) + case "CandidateList": + d.handleCandidateList(w, r) } } diff --git a/internal/rootmux/admin_test.go b/internal/rootmux/admin_test.go index 68e96e5..29365cf 100644 --- a/internal/rootmux/admin_test.go +++ b/internal/rootmux/admin_test.go @@ -101,8 +101,19 @@ func AdminTests(t *testing.T) { // Update a Party do("PUT", "/api/Party/1", xsrfHeader, `{"ID":1,"Name":"MyBigParty","Color":"#000000"}`, 200, `{"ID":1,"Name":"MyBigParty","Color":"#000000","CandidateLists":[]}`) + // Create a CandidateList + do("POST", "/api/CandidateList", xsrfHeader, `{"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1}`, 200, `{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}`) + // Get a CandidateList + do("GET", "/api/CandidateList/1", xsrfHeader, ``, 200, `{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}`) + // Get CandidateLists + do("GET", "/api/CandidateList/", xsrfHeader, ``, 200, `[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}]`) + // Update a CandidateList + do("PUT", "/api/CandidateList/1", xsrfHeader, `{"ID":1,"Name":"MyBigList","PartyID":1,"RoundID":1,"AreaID":1}`, 200, `{"ID":1,"Name":"MyBigList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}`) + // TODO Update a DeskRound to Validated=true can only be done when votes are captured + // Delete a CandidateList + do("DELETE", "/api/CandidateList/1", xsrfHeader, ``, 200, ``) // Delete a Party do("DELETE", "/api/Party/1", xsrfHeader, ``, 200, ``) // Delete a Round diff --git a/internal/rootmux/capturer_test.go b/internal/rootmux/capturer_test.go index 11b4a51..59c0b48 100644 --- a/internal/rootmux/capturer_test.go +++ b/internal/rootmux/capturer_test.go @@ -82,9 +82,9 @@ func CapturerTests(t *testing.T) { // Create a round should fail with 405 do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 405, `You're not authorize to execute this method on this ressource.`) // Get a desk - do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`) + do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`) // Get all the desks - do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}]`) + do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`) // Update a desk should fail with 405 do("PUT", "/api/Round/1", xsrfHeader, `{"ID":1,"ElectionID":1,"Date":"2020-07-28","Round":2}`, 405, `You're not authorize to execute this method on this ressource.`) // Delete a desk should fail with 405 @@ -104,14 +104,25 @@ func CapturerTests(t *testing.T) { // Create a Party should fail with 405 do("POST", "/api/Party", xsrfHeader, `{"Name":"MyGreatParty","Color":"#FFFFFF"}`, 405, `You're not authorize to execute this method on this ressource.`) // Get a party - do("GET", "/api/Party/1", xsrfHeader, "", 200, `{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[]}`) + do("GET", "/api/Party/1", xsrfHeader, "", 200, `{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`) // Get all the parties - do("GET", "/api/Party/", xsrfHeader, "", 200, `[{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[]}]`) + do("GET", "/api/Party/", xsrfHeader, "", 200, `[{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`) // Update a party should fail with 405 do("PUT", "/api/Party/1", xsrfHeader, `{"ID":1,"Name":"MyBigParty","Color":"#000000"}`, 405, `You're not authorize to execute this method on this ressource.`) // Delete a party should fail with 405 do("DELETE", "/api/Party/1", xsrfHeader, ``, 405, `You're not authorize to execute this method on this ressource.`) + // Create a CandidateList should fail with 405 + do("POST", "/api/CandidateList", xsrfHeader, `{"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1}`, 405, `You're not authorize to execute this method on this ressource.`) + // Get a candidateList + do("GET", "/api/CandidateList/1", xsrfHeader, "", 200, `{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}`) + // Get all the parties + do("GET", "/api/CandidateList/", xsrfHeader, "", 200, `[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}]`) + // Update a CandidateList should fail with 405 + do("PUT", "/api/CandidateList/1", xsrfHeader, `{"ID":1,"Name":"MyBigList","PartyID":1,"RoundID":1,"AreaID":1}`, 405, `You're not authorize to execute this method on this ressource.`) + // Delete a CandidateList should fail with 405 + do("DELETE", "/api/CandidateList/1", xsrfHeader, ``, 405, `You're not authorize to execute this method on this ressource.`) + // Add deskround to capturer should fail with 405 do("POST", "/api/CapturerDeskRound", xsrfHeader, `{"CapturerID":1,"DeskRoundID":1}`, 405, `You're not authorize to execute this method on this ressource.`) // Remove DeskRound to capturer diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 11becea..8221960 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -95,7 +95,7 @@ func appTests(t *testing.T) { do("POST", "/api/Capturer", xsrfHeader, `{"UserID":2,"Name":"Capturer"}`, 500, `UserID is already bind to a Capturer`) // Verify that RoundDesks have been created on Round Creation - do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`) + do("GET", "/api/Round/1", xsrfHeader, ``, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`) do("GET", "/api/Desk/1", xsrfHeader, ``, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}]}`) // Verify that a DeskRound can't be validated witout being completed @@ -127,6 +127,8 @@ func deletionInCascadeTest(t *testing.T) { json.Unmarshal([]byte(response), &token) xsrfHeader := tester.Header{Key: "XSRF-TOKEN", Value: token.XSRFToken} + // TODO check that Candidate and candidateLists are deleted in cascade on Party deletion. + // Test deletion in cascade for generic election do("DELETE", "/api/Election/1", xsrfHeader, ``, 200, ``) do("GET", "/api/Area/1", xsrfHeader, ``, 404, `id is missing`) @@ -219,6 +221,7 @@ func resetDataWithData(t *testing.T) { do("POST", "/api/Desk", xsrfHeader, `{"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587}`, 200, `{"ID":1,"SectionID":1,"Name":"Desk 1","WitnessDesk":true,"Subscribed":9587,"DeskRounds":null}`) do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":null,"CandidateLists":null}`) do("POST", "/api/Party", xsrfHeader, `{"Name":"MyGreatParty","Color":"#FFFFFF"}`, 200, `{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":null}`) + do("POST", "/api/CandidateList", xsrfHeader, `{"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1}`, 200, `{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}`) } do("POST", "/Login", noH, `{"login": "admin","password": "password"}`, 200, "") init() diff --git a/internal/rootmux/visualizer_test.go b/internal/rootmux/visualizer_test.go index e798698..56eeee6 100644 --- a/internal/rootmux/visualizer_test.go +++ b/internal/rootmux/visualizer_test.go @@ -69,9 +69,9 @@ func VisualizerTests(t *testing.T) { // Create a round should fail with 405 do("POST", "/api/Round", xsrfHeader, `{"ElectionID":1,"Date":"2020-06-28","Round":1}`, 405, `You're not authorize to execute this method on this ressource.`) // Get a desk - do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}`) + do("GET", "/api/Round/1", xsrfHeader, "", 200, `{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`) // Get all the desks - do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[]}]`) + do("GET", "/api/Round/", xsrfHeader, "", 200, `[{"ID":1,"ElectionID":1,"Parameter":{"ID":0,"CountBlankAndNull":false,"ShowOnlyCompleted":false,"ShowMap":false},"Date":"2020-06-28","Round":1,"DeskRounds":[{"ID":1,"RoundID":1,"DeskID":1,"Capturers":null,"Completed":false,"DateCompletion":"0001-01-01T00:00:00Z","Validated":false,"Votes":null}],"CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`) // Update a desk should fail with 405 do("PUT", "/api/Round/1", xsrfHeader, `{"ID":1,"ElectionID":1,"Date":"2020-07-28","Round":2}`, 405, `You're not authorize to execute this method on this ressource.`) // Delete a desk should fail with 405 @@ -91,14 +91,25 @@ func VisualizerTests(t *testing.T) { // Create a Party should fail with 405 do("POST", "/api/Party", xsrfHeader, `{"Name":"MyGreatParty","Color":"#FFFFFF"}`, 405, `You're not authorize to execute this method on this ressource.`) // Get a party - do("GET", "/api/Party/1", xsrfHeader, "", 200, `{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[]}`) + do("GET", "/api/Party/1", xsrfHeader, "", 200, `{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}`) // Get all the parties - do("GET", "/api/Party/", xsrfHeader, "", 200, `[{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[]}]`) + do("GET", "/api/Party/", xsrfHeader, "", 200, `[{"ID":1,"Name":"MyGreatParty","Color":"#FFFFFF","CandidateLists":[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":null,"Votes":null}]}]`) // Update a party should fail with 405 do("PUT", "/api/Party/1", xsrfHeader, `{"ID":1,"Name":"MyBigParty","Color":"#000000"}`, 405, `You're not authorize to execute this method on this ressource.`) // Delete a party should fail with 405 do("DELETE", "/api/Party/1", xsrfHeader, ``, 405, `You're not authorize to execute this method on this ressource.`) + // Create a CandidateList should fail with 405 + do("POST", "/api/CandidateList", xsrfHeader, `{"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1}`, 405, `You're not authorize to execute this method on this ressource.`) + // Get a candidateList + do("GET", "/api/CandidateList/1", xsrfHeader, "", 200, `{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}`) + // Get all the parties + do("GET", "/api/CandidateList/", xsrfHeader, "", 200, `[{"ID":1,"Name":"MyGreatList","PartyID":1,"RoundID":1,"AreaID":1,"Candidates":[],"Votes":[]}]`) + // Update a CandidateList should fail with 405 + do("PUT", "/api/CandidateList/1", xsrfHeader, `{"ID":1,"Name":"MyBigList","PartyID":1,"RoundID":1,"AreaID":1}`, 405, `You're not authorize to execute this method on this ressource.`) + // Delete a CandidateList should fail with 405 + do("DELETE", "/api/CandidateList/1", xsrfHeader, ``, 405, `You're not authorize to execute this method on this ressource.`) + // Add deskround to capturer should fail with 405 do("POST", "/api/CapturerDeskRound", xsrfHeader, `{"CapturerID":1,"DeskRoundID":1}`, 405, `You're not authorize to execute this method on this ressource.`) // Remove DeskRound to capturer -- GitLab