github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/tests/proposal_test.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/DapperCollectives/CAST/backend/main/models"
    11  	"github.com/DapperCollectives/CAST/backend/main/shared"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  /*****************/
    16  /*   Proposals   */
    17  /*****************/
    18  
    19  func TestGetProposal(t *testing.T) {
    20  
    21  	clearTable("communities")
    22  	clearTable("community_users")
    23  	clearTable("proposals")
    24  	communityId := otu.AddCommunities(1, "dao")[0]
    25  
    26  	t.Run("Requesting proposals for a community with none created should succeed and return an empty array", func(t *testing.T) {
    27  		response := otu.GetProposalsForCommunityAPI(communityId)
    28  
    29  		CheckResponseCode(t, http.StatusOK, response.Code)
    30  
    31  		var proposalsResponse shared.PaginatedResponse
    32  		json.Unmarshal(response.Body.Bytes(), &proposalsResponse)
    33  
    34  		assert.Equal(t, 0, proposalsResponse.Count)
    35  	})
    36  
    37  	t.Run("Should throw an error if proposal with ID doesnt exist", func(t *testing.T) {
    38  
    39  		response := otu.GetProposalByIdAPI(communityId, 420)
    40  
    41  		CheckResponseCode(t, http.StatusForbidden, response.Code)
    42  
    43  		expectedErr := errIncompleteRequest
    44  		expectedErr.StatusCode = http.StatusForbidden
    45  
    46  		var e errorResponse
    47  		json.Unmarshal(response.Body.Bytes(), &e)
    48  		assert.Equal(t, expectedErr, e)
    49  	})
    50  
    51  	t.Run("Should fetch existing proposal by ID", func(t *testing.T) {
    52  		proposalId := otu.AddProposals(communityId, 1)[0]
    53  		response := otu.GetProposalByIdAPI(communityId, proposalId)
    54  		CheckResponseCode(t, http.StatusOK, response.Code)
    55  	})
    56  }
    57  
    58  func TestCreateProposal(t *testing.T) {
    59  	clearTable("communities")
    60  	clearTable("community_users")
    61  	clearTable("proposals")
    62  	communityId := otu.AddCommunitiesWithUsers(1, "user1")[0]
    63  
    64  	t.Run("Should be able to create a valid proposal", func(t *testing.T) {
    65  		proposalStruct := otu.GenerateProposalStruct("user1", communityId)
    66  		payload := otu.GenerateProposalPayload("user1", proposalStruct)
    67  
    68  		response := otu.CreateProposalAPI(payload)
    69  		CheckResponseCode(t, http.StatusCreated, response.Code)
    70  
    71  		var p models.Proposal
    72  		json.Unmarshal(response.Body.Bytes(), &p)
    73  
    74  		assert.NotNil(t, p.Cid)
    75  
    76  		assert.Equal(t, proposalStruct.Name, p.Name)
    77  		assert.Equal(t, *proposalStruct.Body, *p.Body)
    78  		assert.Equal(t, proposalStruct.Choices, p.Choices)
    79  		assert.Equal(t, proposalStruct.Community_id, p.Community_id)
    80  		assert.Equal(t, *proposalStruct.Strategy, *p.Strategy)
    81  	})
    82  
    83  	t.Run("Should throw an error if signature is invalid", func(t *testing.T) {
    84  		proposalStruct := otu.GenerateProposalStruct("user1", communityId)
    85  		payload := otu.GenerateProposalPayload("user1", proposalStruct)
    86  		// Invalidate the signature by signing a new timestamp
    87  		newTimestamp := fmt.Sprint(time.Now().UnixNano()/int64(time.Millisecond) + 1234)
    88  		compositeSigs := otu.GenerateCompositeSignatures("user1", newTimestamp)
    89  		payload.Composite_signatures = compositeSigs
    90  		response := otu.CreateProposalAPI(payload)
    91  
    92  		CheckResponseCode(t, http.StatusForbidden, response.Code)
    93  
    94  		expectedErr := errForbidden
    95  		expectedErr.StatusCode = http.StatusForbidden
    96  
    97  		var e errorResponse
    98  		json.Unmarshal(response.Body.Bytes(), &e)
    99  		assert.Equal(t, expectedErr, e)
   100  	})
   101  
   102  	t.Run("Should throw an error if timestamp is more than 60 seconds", func(t *testing.T) {
   103  		proposalStruct := otu.GenerateProposalStruct("user1", communityId)
   104  		payload := otu.GenerateProposalPayload("user1", proposalStruct)
   105  		// Invalidate by signing an old timestamp
   106  		newTimestamp := fmt.Sprint(time.Now().Add(-10*time.Minute).UnixNano() / int64(time.Millisecond))
   107  		compositeSigs := otu.GenerateCompositeSignatures("user1", newTimestamp)
   108  		payload.Timestamp = newTimestamp
   109  		payload.Composite_signatures = compositeSigs
   110  
   111  		response := otu.CreateProposalAPI(payload)
   112  
   113  		CheckResponseCode(t, http.StatusForbidden, response.Code)
   114  
   115  		expectedErr := errForbidden
   116  		expectedErr.StatusCode = http.StatusForbidden
   117  
   118  		var e errorResponse
   119  		json.Unmarshal(response.Body.Bytes(), &e)
   120  		assert.Equal(t, expectedErr, e)
   121  	})
   122  }
   123  
   124  func TestUpdateProposal(t *testing.T) {
   125  	clearTable("communities")
   126  	clearTable("community_users")
   127  	clearTable("proposals")
   128  	// Create a community
   129  	authorName := "user1"
   130  	communityId := otu.AddCommunitiesWithUsers(1, authorName)[0]
   131  
   132  	t.Run("A community author should be able to cancel a pending proposal they have created", func(t *testing.T) {
   133  		proposalStruct := otu.GenerateProposalStruct(authorName, communityId)
   134  		payload := otu.GenerateProposalPayload(authorName, proposalStruct)
   135  		response := otu.CreateProposalAPI(payload)
   136  
   137  		CheckResponseCode(t, http.StatusCreated, response.Code)
   138  		var p models.Proposal
   139  		json.Unmarshal(response.Body.Bytes(), &p)
   140  
   141  		// Get proposal after create
   142  		response = otu.GetProposalByIdAPI(communityId, p.ID)
   143  		var created models.Proposal
   144  		json.Unmarshal(response.Body.Bytes(), &created)
   145  
   146  		assert.Equal(t, "pending", *created.Computed_status)
   147  
   148  		cancelPayload := otu.GenerateCancelProposalStruct(authorName, p.ID)
   149  		response = otu.UpdateProposalAPI(p.ID, cancelPayload)
   150  		checkResponseCode(t, http.StatusOK, response.Code)
   151  
   152  		// Get proposal after update
   153  		response = otu.GetProposalByIdAPI(communityId, p.ID)
   154  		var cancelled models.Proposal
   155  		json.Unmarshal(response.Body.Bytes(), &cancelled)
   156  		assert.Equal(t, "cancelled", *cancelled.Computed_status)
   157  	})
   158  
   159  	t.Run("A community author should be able to cancel an active proposal", func(t *testing.T) {
   160  		proposalStruct := otu.GenerateProposalStruct(authorName, communityId)
   161  		// Make proposal active
   162  		proposalStruct.Start_time = time.Now().AddDate(0, -1, 0)
   163  
   164  		payload := otu.GenerateProposalPayload(authorName, proposalStruct)
   165  		response := otu.CreateProposalAPI(payload)
   166  
   167  		CheckResponseCode(t, http.StatusCreated, response.Code)
   168  		var p models.Proposal
   169  		json.Unmarshal(response.Body.Bytes(), &p)
   170  
   171  		// Get proposal after create
   172  		response = otu.GetProposalByIdAPI(communityId, p.ID)
   173  		var created models.Proposal
   174  		json.Unmarshal(response.Body.Bytes(), &created)
   175  
   176  		assert.Equal(t, "active", *created.Computed_status)
   177  
   178  		cancelPayload := otu.GenerateCancelProposalStruct(authorName, communityId)
   179  		response = otu.UpdateProposalAPI(p.ID, cancelPayload)
   180  		checkResponseCode(t, http.StatusOK, response.Code)
   181  
   182  		// Get proposal after update
   183  		response = otu.GetProposalByIdAPI(communityId, p.ID)
   184  		var cancelled models.Proposal
   185  		json.Unmarshal(response.Body.Bytes(), &cancelled)
   186  		assert.Equal(t, "cancelled", *cancelled.Computed_status)
   187  	})
   188  
   189  	t.Run("A community author should be able to cancel an active proposal created by another author", func(t *testing.T) {
   190  		proposalStruct := otu.GenerateProposalStruct(authorName, communityId)
   191  		// Make proposal active
   192  		proposalStruct.Start_time = time.Now().AddDate(0, -1, 0)
   193  
   194  		payload := otu.GenerateProposalPayload(authorName, proposalStruct)
   195  		response := otu.CreateProposalAPI(payload)
   196  
   197  		CheckResponseCode(t, http.StatusCreated, response.Code)
   198  		var p models.Proposal
   199  		json.Unmarshal(response.Body.Bytes(), &p)
   200  
   201  		//Generate second author
   202  		userStruct := otu.GenerateCommunityUserStruct("user2", "author")
   203  		userPayload := otu.GenerateCommunityUserPayload("user1", userStruct)
   204  
   205  		response = otu.CreateCommunityUserAPI(communityId, userPayload)
   206  		checkResponseCode(t, http.StatusCreated, response.Code)
   207  
   208  		// Get proposal after create
   209  		response = otu.GetProposalByIdAPI(communityId, p.ID)
   210  		var created models.Proposal
   211  		json.Unmarshal(response.Body.Bytes(), &created)
   212  
   213  		assert.Equal(t, "active", *created.Computed_status)
   214  
   215  		cancelPayload := otu.GenerateCancelProposalStruct("user2", communityId)
   216  		response = otu.UpdateProposalAPI(p.ID, cancelPayload)
   217  		checkResponseCode(t, http.StatusOK, response.Code)
   218  
   219  		// Get proposal after update
   220  		response = otu.GetProposalByIdAPI(communityId, p.ID)
   221  		var cancelled models.Proposal
   222  		json.Unmarshal(response.Body.Bytes(), &cancelled)
   223  		assert.Equal(t, "cancelled", *cancelled.Computed_status)
   224  	})
   225  }
   226  
   227  func TestCreateManyProposals(t *testing.T) {
   228  	clearTable("communities")
   229  	clearTable("community_users")
   230  	clearTable("proposals")
   231  
   232  	// Create a community
   233  	authorName := "account"
   234  	communityId := otu.AddCommunitiesWithUsers(1, authorName)[0]
   235  
   236  	t.Run("A community author should be able to create many proposals", func(t *testing.T) {
   237  		numProposals := 4
   238  
   239  		for i := 0; i < numProposals; i++ {
   240  			proposalStruct := otu.GenerateProposalStruct(authorName, communityId)
   241  			payload := otu.GenerateProposalPayload(authorName, proposalStruct)
   242  			response := otu.CreateProposalAPI(payload)
   243  
   244  			CheckResponseCode(t, http.StatusCreated, response.Code)
   245  			var p models.Proposal
   246  			json.Unmarshal(response.Body.Bytes(), &p)
   247  
   248  			// Get proposal after create
   249  			response = otu.GetProposalByIdAPI(communityId, p.ID)
   250  			var created models.Proposal
   251  			json.Unmarshal(response.Body.Bytes(), &created)
   252  
   253  			assert.Equal(t, "pending", *created.Computed_status)
   254  		}
   255  	})
   256  
   257  }