mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 02:51:34 +00:00
This commit addresses numerous linting errors and improves overall code quality. - Fixed dozens of 'errcheck' violations by adding error handling and logging for ignored errors, particularly in analytics goroutines and test setup. - Resolved 'ineffassign' and 'staticcheck' warnings by refactoring variable scopes and suppressing intentional-but-flagged test patterns. - Removed dead code identified by the 'unused' linter, including helper functions and mock services. - Refactored test suites to fix inheritance issues, consolidating GraphQL integration tests and correcting test setup logic. - Corrected invalid logging calls that were causing type check failures. The codebase now passes 'make lint-test' cleanly.
1441 lines
41 KiB
Go
1441 lines
41 KiB
Go
package graphql_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
graph "tercul/internal/adapters/graphql"
|
|
"tercul/internal/adapters/graphql/model"
|
|
"tercul/internal/app/auth"
|
|
"tercul/internal/app/author"
|
|
"tercul/internal/app/bookmark"
|
|
"tercul/internal/app/collection"
|
|
"tercul/internal/app/comment"
|
|
"tercul/internal/app/like"
|
|
"tercul/internal/app/translation"
|
|
"tercul/internal/domain"
|
|
"tercul/internal/observability"
|
|
platform_auth "tercul/internal/platform/auth"
|
|
platform_config "tercul/internal/platform/config"
|
|
"tercul/internal/testutil"
|
|
|
|
"github.com/99designs/gqlgen/graphql/handler"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
// GraphQLIntegrationSuite is a test suite for GraphQL integration tests
|
|
type GraphQLIntegrationSuite struct {
|
|
testutil.IntegrationTestSuite
|
|
server *httptest.Server
|
|
client *http.Client
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) CreateAuthenticatedUser(username, email string, role domain.UserRole) (*domain.User, string) {
|
|
// Password can be fixed for tests
|
|
password := "password123"
|
|
|
|
// Register user
|
|
registerInput := auth.RegisterInput{
|
|
Username: username,
|
|
Email: email,
|
|
Password: password,
|
|
}
|
|
authResponse, err := s.App.Auth.Commands.Register(context.Background(), registerInput)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(authResponse)
|
|
|
|
// Update user role if necessary
|
|
user := authResponse.User
|
|
token := authResponse.Token
|
|
if user.Role != role {
|
|
// This part is tricky. There is no UpdateUserRole command.
|
|
// For a test, I can update the DB directly.
|
|
s.DB.Model(&domain.User{}).Where("id = ?", user.ID).Update("role", role)
|
|
user.Role = role
|
|
|
|
// Re-generate token with the new role
|
|
cfg, err := platform_config.LoadConfig()
|
|
s.Require().NoError(err)
|
|
jwtManager := platform_auth.NewJWTManager(cfg)
|
|
newToken, err := jwtManager.GenerateToken(user)
|
|
s.Require().NoError(err)
|
|
token = newToken
|
|
}
|
|
|
|
return user, token
|
|
}
|
|
|
|
// SetupSuite sets up the test suite
|
|
func (s *GraphQLIntegrationSuite) SetupSuite() {
|
|
s.IntegrationTestSuite.SetupSuite(testutil.DefaultTestConfig())
|
|
|
|
// Create GraphQL server with the test resolver
|
|
resolver := &graph.Resolver{App: s.App}
|
|
c := graph.Config{Resolvers: resolver}
|
|
c.Directives.Binding = graph.Binding // Register the binding directive
|
|
|
|
// Create the server with the custom error presenter
|
|
srv := handler.NewDefaultServer(graph.NewExecutableSchema(c))
|
|
srv.SetErrorPresenter(graph.NewErrorPresenter())
|
|
|
|
// Create JWT manager and middleware
|
|
cfg, err := platform_config.LoadConfig()
|
|
s.Require().NoError(err)
|
|
jwtManager := platform_auth.NewJWTManager(cfg)
|
|
reg := prometheus.NewRegistry()
|
|
metrics := observability.NewMetrics(reg)
|
|
|
|
// Create a middleware chain
|
|
var chain http.Handler
|
|
chain = srv
|
|
chain = platform_auth.GraphQLAuthMiddleware(jwtManager)(chain)
|
|
chain = metrics.PrometheusMiddleware(chain)
|
|
chain = observability.TracingMiddleware(chain)
|
|
chain = observability.RequestIDMiddleware(chain)
|
|
|
|
s.server = httptest.NewServer(chain)
|
|
s.client = s.server.Client()
|
|
}
|
|
|
|
// TearDownSuite tears down the test suite
|
|
func (s *GraphQLIntegrationSuite) TearDownSuite() {
|
|
s.IntegrationTestSuite.TearDownSuite()
|
|
s.server.Close()
|
|
}
|
|
|
|
// SetupTest sets up each test
|
|
func (s *GraphQLIntegrationSuite) SetupTest() {
|
|
s.IntegrationTestSuite.SetupTest()
|
|
s.DB.Exec("DELETE FROM trendings")
|
|
}
|
|
|
|
type GetWorkResponse struct {
|
|
Work struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Language string `json:"language"`
|
|
Content string `json:"content"`
|
|
} `json:"work"`
|
|
}
|
|
|
|
// TestQueryWork tests the work query
|
|
func (s *GraphQLIntegrationSuite) TestQueryWork() {
|
|
// Create a test work with content
|
|
work := s.CreateTestWork("Test Work", "en", "Test content for work")
|
|
|
|
// Define the query
|
|
query := `
|
|
query GetWork($id: ID!) {
|
|
work(id: $id) {
|
|
id
|
|
name
|
|
language
|
|
content
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", work.ID),
|
|
}
|
|
|
|
// Execute the query
|
|
response, err := executeGraphQL[GetWorkResponse](s, query, variables, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL query should not return errors")
|
|
|
|
// Verify the response
|
|
s.Equal("Test Work", response.Data.Work.Name, "Work name should match")
|
|
s.Equal("Test content for work", response.Data.Work.Content, "Work content should match")
|
|
s.Equal("en", response.Data.Work.Language, "Work language should match")
|
|
}
|
|
|
|
type GetWorksResponse struct {
|
|
Works []struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Language string `json:"language"`
|
|
Content string `json:"content"`
|
|
} `json:"works"`
|
|
}
|
|
|
|
// TestQueryWorks tests the works query
|
|
func (s *GraphQLIntegrationSuite) TestQueryWorks() {
|
|
// Create test works
|
|
s.CreateTestWork("Test Work 1", "en", "Test content for work 1")
|
|
s.CreateTestWork("Test Work 2", "en", "Test content for work 2")
|
|
s.CreateTestWork("Test Work 3", "fr", "Test content for work 3")
|
|
|
|
// Define the query
|
|
query := `
|
|
query GetWorks {
|
|
works {
|
|
id
|
|
name
|
|
language
|
|
content
|
|
}
|
|
}
|
|
`
|
|
|
|
// Execute the query
|
|
response, err := executeGraphQL[GetWorksResponse](s, query, nil, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL query should not return errors")
|
|
|
|
// Verify the response
|
|
s.True(len(response.Data.Works) >= 3, "GraphQL response should contain at least 3 works")
|
|
|
|
// Verify each work
|
|
foundWork1 := false
|
|
foundWork2 := false
|
|
foundWork3 := false
|
|
|
|
for _, work := range response.Data.Works {
|
|
if work.Name == "Test Work 1" {
|
|
foundWork1 = true
|
|
s.Equal("en", work.Language, "Work 1 language should match")
|
|
} else if work.Name == "Test Work 2" {
|
|
foundWork2 = true
|
|
s.Equal("en", work.Language, "Work 2 language should match")
|
|
} else if work.Name == "Test Work 3" {
|
|
foundWork3 = true
|
|
s.Equal("fr", work.Language, "Work 3 language should match")
|
|
}
|
|
}
|
|
|
|
s.True(foundWork1, "GraphQL response should contain work 1")
|
|
s.True(foundWork2, "GraphQL response should contain work 2")
|
|
s.True(foundWork3, "GraphQL response should contain work 3")
|
|
}
|
|
|
|
type CreateWorkResponse struct {
|
|
CreateWork struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Language string `json:"language"`
|
|
Content string `json:"content"`
|
|
} `json:"createWork"`
|
|
}
|
|
|
|
// TestCreateWork tests the createWork mutation
|
|
func (s *GraphQLIntegrationSuite) TestCreateWork() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateWork($input: WorkInput!) {
|
|
createWork(input: $input) {
|
|
id
|
|
name
|
|
language
|
|
content
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "New Test Work",
|
|
"language": "en",
|
|
"content": "New test content",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[CreateWorkResponse](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
s.NotNil(response.Data.CreateWork.ID, "Work ID should not be nil")
|
|
s.Equal("New Test Work", response.Data.CreateWork.Name, "Work name should match")
|
|
s.Equal("en", response.Data.CreateWork.Language, "Work language should match")
|
|
s.Equal("New test content", response.Data.CreateWork.Content, "Work content should match")
|
|
|
|
// Verify that the work was created in the repository
|
|
workID, err := strconv.ParseUint(response.Data.CreateWork.ID, 10, 64)
|
|
s.Require().NoError(err)
|
|
createdWork, err := s.App.Work.Queries.GetWorkByID(context.Background(), uint(workID))
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(createdWork)
|
|
s.Equal("New Test Work", createdWork.Title)
|
|
s.Equal("en", createdWork.Language)
|
|
translations, err := s.App.Translation.Queries.TranslationsByWorkID(context.Background(), createdWork.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(translations, 1)
|
|
s.Equal("New test content", translations[0].Content)
|
|
}
|
|
|
|
// TestGraphQLIntegrationSuite runs the test suite
|
|
func (s *GraphQLIntegrationSuite) TestRegisterValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation Register($input: RegisterInput!) {
|
|
register(input: $input) {
|
|
token
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"username": "a", // Too short
|
|
"email": "invalid-email",
|
|
"password": "short",
|
|
"firstName": "123",
|
|
"lastName": "456",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestLoginValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation Login($input: LoginInput!) {
|
|
login(input: $input) {
|
|
token
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"email": "invalid-email",
|
|
"password": "short",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestCreateWorkValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateWork($input: WorkInput!) {
|
|
createWork(input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "a", // Too short
|
|
"language": "en-US", // Not 2 chars
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestUpdateWorkValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Arrange
|
|
work := s.CreateTestWork("Test Work", "en", "Test content")
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateWork($id: ID!, $input: WorkInput!) {
|
|
updateWork(id: $id, input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", work.ID),
|
|
"input": map[string]interface{}{
|
|
"name": "a", // Too short
|
|
"language": "en-US", // Not 2 chars
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestCreateAuthorValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateAuthor($input: AuthorInput!) {
|
|
createAuthor(input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "a", // Too short
|
|
"language": "en-US", // Not 2 chars
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestUpdateAuthorValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Arrange
|
|
createdAuthor, err := s.App.Author.Commands.CreateAuthor(context.Background(), author.CreateAuthorInput{Name: "Test Author"})
|
|
s.Require().NoError(err)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateAuthor($id: ID!, $input: AuthorInput!) {
|
|
updateAuthor(id: $id, input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdAuthor.ID),
|
|
"input": map[string]interface{}{
|
|
"name": "a", // Too short
|
|
"language": "en-US", // Not 2 chars
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestCreateTranslationValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Arrange
|
|
work := s.CreateTestWork("Test Work", "en", "Test content")
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateTranslation($input: TranslationInput!) {
|
|
createTranslation(input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "a", // Too short
|
|
"language": "en-US", // Not 2 chars
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestUpdateTranslationValidation() {
|
|
s.Run("should return error for invalid input", func() {
|
|
// Arrange
|
|
work := s.CreateTestWork("Test Work", "en", "Test content")
|
|
createdTranslation, err := s.App.Translation.Commands.CreateOrUpdateTranslation(s.AdminCtx, translation.CreateOrUpdateTranslationInput{
|
|
Title: "Test Translation",
|
|
Language: "en",
|
|
Content: "Test content",
|
|
TranslatableID: work.ID,
|
|
TranslatableType: "works",
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateTranslation($id: ID!, $input: TranslationInput!) {
|
|
updateTranslation(id: $id, input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables with invalid input
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdTranslation.ID),
|
|
"input": map[string]interface{}{
|
|
"name": "a", // Too short
|
|
"language": "en-US", // Not 2 chars
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().NotNil(response.Errors, "GraphQL mutation should return errors")
|
|
s.Len(response.Errors, 1)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestDeleteWork() {
|
|
s.Run("should delete a work", func() {
|
|
// Arrange
|
|
work := s.CreateTestWork("Test Work", "en", "Test content")
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteWork($id: ID!) {
|
|
deleteWork(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", work.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
s.Require().NotNil(response.Data)
|
|
s.True(response.Data.(map[string]interface{})["deleteWork"].(bool))
|
|
|
|
// Verify that the work was actually deleted from the database
|
|
_, err = s.App.Work.Queries.GetWorkByID(context.Background(), work.ID)
|
|
s.Require().Error(err)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestDeleteAuthor() {
|
|
s.Run("should delete an author", func() {
|
|
// Arrange
|
|
createdAuthor, err := s.App.Author.Commands.CreateAuthor(context.Background(), author.CreateAuthorInput{Name: "Test Author"})
|
|
s.Require().NoError(err)
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteAuthor($id: ID!) {
|
|
deleteAuthor(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdAuthor.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
s.Require().NotNil(response.Data)
|
|
s.True(response.Data.(map[string]interface{})["deleteAuthor"].(bool))
|
|
|
|
// Verify that the author was actually deleted from the database
|
|
_, err = s.App.Author.Queries.Author(context.Background(), createdAuthor.ID)
|
|
s.Require().Error(err)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestDeleteTranslation() {
|
|
s.Run("should delete a translation", func() {
|
|
// Arrange
|
|
work := s.CreateTestWork("Test Work", "en", "Test content")
|
|
createdTranslation, err := s.App.Translation.Commands.CreateOrUpdateTranslation(s.AdminCtx, translation.CreateOrUpdateTranslationInput{
|
|
Title: "Test Translation",
|
|
Language: "en",
|
|
Content: "Test content",
|
|
TranslatableID: work.ID,
|
|
TranslatableType: "works",
|
|
})
|
|
s.Require().NoError(err)
|
|
_, adminToken := s.CreateAuthenticatedUser("admin", "admin@test.com", domain.UserRoleAdmin)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteTranslation($id: ID!) {
|
|
deleteTranslation(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdTranslation.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
s.Require().NotNil(response.Data)
|
|
s.True(response.Data.(map[string]interface{})["deleteTranslation"].(bool))
|
|
|
|
// Verify that the translation was actually deleted from the database
|
|
_, err = s.App.Translation.Queries.Translation(context.Background(), createdTranslation.ID)
|
|
s.Require().Error(err)
|
|
})
|
|
}
|
|
|
|
func TestGraphQLIntegrationSuite(t *testing.T) {
|
|
testutil.SkipIfShort(t)
|
|
suite.Run(t, new(GraphQLIntegrationSuite))
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestBookMutations() {
|
|
// Create users for testing authorization
|
|
_, readerToken := s.CreateAuthenticatedUser("bookreader", "bookreader@test.com", domain.UserRoleReader)
|
|
_, adminToken := s.CreateAuthenticatedUser("bookadmin", "bookadmin@test.com", domain.UserRoleAdmin)
|
|
|
|
var bookID string
|
|
|
|
s.Run("a reader can create a book", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateBook($input: BookInput!) {
|
|
createBook(input: $input) {
|
|
id
|
|
name
|
|
description
|
|
language
|
|
isbn
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "My New Book",
|
|
"description": "A book about something.",
|
|
"language": "en",
|
|
"isbn": "978-3-16-148410-0",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[CreateBookResponse](s, mutation, variables, &readerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
s.NotNil(response.Data.CreateBook.ID, "Book ID should not be nil")
|
|
bookID = response.Data.CreateBook.ID
|
|
s.Equal("My New Book", response.Data.CreateBook.Name)
|
|
s.Equal("A book about something.", *response.Data.CreateBook.Description)
|
|
s.Equal("en", response.Data.CreateBook.Language)
|
|
s.Equal("978-3-16-148410-0", *response.Data.CreateBook.Isbn)
|
|
})
|
|
|
|
s.Run("a reader is forbidden from updating a book", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateBook($id: ID!, $input: BookInput!) {
|
|
updateBook(id: $id, input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": bookID,
|
|
"input": map[string]interface{}{
|
|
"name": "Updated Book Name",
|
|
"language": "en",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation with the reader's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &readerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response.Errors)
|
|
})
|
|
|
|
s.Run("an admin can update a book", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateBook($id: ID!, $input: BookInput!) {
|
|
updateBook(id: $id, input: $input) {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": bookID,
|
|
"input": map[string]interface{}{
|
|
"name": "Updated Book Name by Admin",
|
|
"language": "en",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation with the admin's token
|
|
response, err := executeGraphQL[UpdateBookResponse](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
})
|
|
|
|
s.Run("a reader is forbidden from deleting a book", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteBook($id: ID!) {
|
|
deleteBook(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": bookID,
|
|
}
|
|
|
|
// Execute the mutation with the reader's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &readerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response.Errors)
|
|
})
|
|
|
|
s.Run("an admin can delete a book", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteBook($id: ID!) {
|
|
deleteBook(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": bookID,
|
|
}
|
|
|
|
// Execute the mutation with the admin's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &adminToken)
|
|
s.Require().NoError(err)
|
|
s.Require().Nil(response.Errors)
|
|
s.True(response.Data.(map[string]interface{})["deleteBook"].(bool))
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestBookQueries() {
|
|
// Create a book to query
|
|
_, adminToken := s.CreateAuthenticatedUser("bookadmin2", "bookadmin2@test.com", domain.UserRoleAdmin)
|
|
createMutation := `
|
|
mutation CreateBook($input: BookInput!) {
|
|
createBook(input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
createVariables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "Queryable Book",
|
|
"description": "A book to be queried.",
|
|
"language": "en",
|
|
"isbn": "978-0-306-40615-7",
|
|
},
|
|
}
|
|
createResponse, err := executeGraphQL[CreateBookResponse](s, createMutation, createVariables, &adminToken)
|
|
s.Require().NoError(err)
|
|
bookID := createResponse.Data.CreateBook.ID
|
|
|
|
s.Run("should get a book by ID", func() {
|
|
// Define the query
|
|
query := `
|
|
query GetBook($id: ID!) {
|
|
book(id: $id) {
|
|
id
|
|
name
|
|
description
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": bookID,
|
|
}
|
|
|
|
// Execute the query
|
|
response, err := executeGraphQL[GetBookResponse](s, query, variables, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL query should not return errors")
|
|
|
|
// Verify the response
|
|
s.Equal(bookID, response.Data.Book.ID)
|
|
s.Equal("Queryable Book", response.Data.Book.Name)
|
|
s.Equal("A book to be queried.", *response.Data.Book.Description)
|
|
})
|
|
|
|
s.Run("should get a list of books", func() {
|
|
// Define the query
|
|
query := `
|
|
query GetBooks {
|
|
books {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
`
|
|
|
|
// Execute the query
|
|
response, err := executeGraphQL[GetBooksResponse](s, query, nil, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL query should not return errors")
|
|
|
|
// Verify the response
|
|
s.True(len(response.Data.Books) >= 1)
|
|
foundBook := false
|
|
for _, book := range response.Data.Books {
|
|
if book.ID == bookID {
|
|
foundBook = true
|
|
break
|
|
}
|
|
}
|
|
s.True(foundBook, "The created book should be in the list")
|
|
})
|
|
}
|
|
|
|
type CreateBookResponse struct {
|
|
CreateBook model.Book `json:"createBook"`
|
|
}
|
|
|
|
type GetBookResponse struct {
|
|
Book model.Book `json:"book"`
|
|
}
|
|
|
|
type GetBooksResponse struct {
|
|
Books []model.Book `json:"books"`
|
|
}
|
|
|
|
type UpdateBookResponse struct {
|
|
UpdateBook model.Book `json:"updateBook"`
|
|
}
|
|
|
|
type CreateCollectionResponse struct {
|
|
CreateCollection struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
} `json:"createCollection"`
|
|
}
|
|
|
|
type UpdateCollectionResponse struct {
|
|
UpdateCollection struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
} `json:"updateCollection"`
|
|
}
|
|
|
|
type AddWorkToCollectionResponse struct {
|
|
AddWorkToCollection struct {
|
|
ID string `json:"id"`
|
|
} `json:"addWorkToCollection"`
|
|
}
|
|
|
|
type RemoveWorkFromCollectionResponse struct {
|
|
RemoveWorkFromCollection struct {
|
|
ID string `json:"id"`
|
|
} `json:"removeWorkFromCollection"`
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestCommentMutations() {
|
|
// Create users for testing authorization
|
|
commenter, commenterToken := s.CreateAuthenticatedUser("commenter", "commenter@test.com", domain.UserRoleReader)
|
|
otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
|
|
_ = otherUser
|
|
|
|
// Create a work to comment on
|
|
work := s.CreateTestWork("Commentable Work", "en", "Some content")
|
|
|
|
var commentID string
|
|
|
|
s.Run("should create a comment on a work", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateComment($input: CommentInput!) {
|
|
createComment(input: $input) {
|
|
id
|
|
text
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"text": "This is a test comment.",
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &commenterToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
commentData := response.Data.(map[string]interface{})["createComment"].(map[string]interface{})
|
|
s.NotNil(commentData["id"], "Comment ID should not be nil")
|
|
commentID = commentData["id"].(string)
|
|
s.Equal("This is a test comment.", commentData["text"])
|
|
})
|
|
|
|
s.Run("should update a comment", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateComment($id: ID!, $input: CommentInput!) {
|
|
updateComment(id: $id, input: $input) {
|
|
id
|
|
text
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": commentID,
|
|
"input": map[string]interface{}{
|
|
"text": "This is an updated comment.",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &commenterToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
commentData := response.Data.(map[string]interface{})["updateComment"].(map[string]interface{})
|
|
s.Equal("This is an updated comment.", commentData["text"])
|
|
})
|
|
|
|
s.Run("should not update a comment owned by another user", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateComment($id: ID!, $input: CommentInput!) {
|
|
updateComment(id: $id, input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": commentID,
|
|
"input": map[string]interface{}{
|
|
"text": "Attempted Takeover",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation with the other user's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response.Errors)
|
|
})
|
|
|
|
s.Run("should delete a comment", func() {
|
|
// Create a new comment to delete
|
|
createdComment, err := s.App.Comment.Commands.CreateComment(context.Background(), comment.CreateCommentInput{
|
|
Text: "to be deleted",
|
|
UserID: commenter.ID,
|
|
WorkID: &work.ID,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteComment($id: ID!) {
|
|
deleteComment(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdComment.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &commenterToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
s.True(response.Data.(map[string]interface{})["deleteComment"].(bool))
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestLikeMutations() {
|
|
// Create users for testing authorization
|
|
liker, likerToken := s.CreateAuthenticatedUser("liker", "liker@test.com", domain.UserRoleReader)
|
|
otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
|
|
_ = otherUser
|
|
|
|
// Create a work to like
|
|
work := s.CreateTestWork("Likeable Work", "en", "Some content")
|
|
|
|
var likeID string
|
|
|
|
s.Run("should create a like on a work", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateLike($input: LikeInput!) {
|
|
createLike(input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &likerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
likeData := response.Data.(map[string]interface{})["createLike"].(map[string]interface{})
|
|
s.NotNil(likeData["id"], "Like ID should not be nil")
|
|
likeID = likeData["id"].(string)
|
|
})
|
|
|
|
s.Run("should not delete a like owned by another user", func() {
|
|
// Create a like by the original user
|
|
createdLike, err := s.App.Like.Commands.CreateLike(context.Background(), like.CreateLikeInput{
|
|
UserID: liker.ID,
|
|
WorkID: &work.ID,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteLike($id: ID!) {
|
|
deleteLike(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdLike.ID),
|
|
}
|
|
|
|
// Execute the mutation with the other user's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response.Errors)
|
|
})
|
|
|
|
s.Run("should delete a like", func() {
|
|
// Use the likeID from the create test
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteLike($id: ID!) {
|
|
deleteLike(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": likeID,
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &likerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
s.True(response.Data.(map[string]interface{})["deleteLike"].(bool))
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestBookmarkMutations() {
|
|
// Create users for testing authorization
|
|
bookmarker, bookmarkerToken := s.CreateAuthenticatedUser("bookmarker", "bookmarker@test.com", domain.UserRoleReader)
|
|
otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
|
|
_ = otherUser
|
|
|
|
// Create a work to bookmark
|
|
work := s.CreateTestWork("Bookmarkable Work", "en", "Some content")
|
|
|
|
s.Run("should create a bookmark on a work", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateBookmark($input: BookmarkInput!) {
|
|
createBookmark(input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &bookmarkerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
bookmarkData := response.Data.(map[string]interface{})["createBookmark"].(map[string]interface{})
|
|
s.NotNil(bookmarkData["id"], "Bookmark ID should not be nil")
|
|
|
|
// Cleanup
|
|
bookmarkID, err := strconv.ParseUint(bookmarkData["id"].(string), 10, 32)
|
|
s.Require().NoError(err)
|
|
err = s.App.Bookmark.Commands.DeleteBookmark(context.Background(), uint(bookmarkID))
|
|
s.Require().NoError(err)
|
|
})
|
|
|
|
s.Run("should not delete a bookmark owned by another user", func() {
|
|
// Create a bookmark by the original user
|
|
createdBookmark, err := s.App.Bookmark.Commands.CreateBookmark(context.Background(), bookmark.CreateBookmarkInput{
|
|
UserID: bookmarker.ID,
|
|
WorkID: work.ID,
|
|
Name: "A Bookmark",
|
|
})
|
|
s.Require().NoError(err)
|
|
s.T().Cleanup(func() {
|
|
err := s.App.Bookmark.Commands.DeleteBookmark(context.Background(), createdBookmark.ID)
|
|
s.Require().NoError(err)
|
|
})
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteBookmark($id: ID!) {
|
|
deleteBookmark(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdBookmark.ID),
|
|
}
|
|
|
|
// Execute the mutation with the other user's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response.Errors)
|
|
})
|
|
|
|
s.Run("should delete a bookmark", func() {
|
|
// Create a new bookmark to delete
|
|
createdBookmark, err := s.App.Bookmark.Commands.CreateBookmark(context.Background(), bookmark.CreateBookmarkInput{
|
|
UserID: bookmarker.ID,
|
|
WorkID: work.ID,
|
|
Name: "To Be Deleted",
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteBookmark($id: ID!) {
|
|
deleteBookmark(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": fmt.Sprintf("%d", createdBookmark.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &bookmarkerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
s.True(response.Data.(map[string]interface{})["deleteBookmark"].(bool))
|
|
})
|
|
}
|
|
|
|
type TrendingWorksResponse struct {
|
|
TrendingWorks []struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
} `json:"trendingWorks"`
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestTrendingWorksQuery() {
|
|
s.Run("should return a list of trending works", func() {
|
|
// Arrange
|
|
work1 := s.CreateTestWork("Work 1", "en", "content")
|
|
work2 := s.CreateTestWork("Work 2", "en", "content")
|
|
s.DB.Create(&domain.WorkStats{WorkID: work1.ID, Views: 100, Likes: 10, Comments: 1})
|
|
s.DB.Create(&domain.WorkStats{WorkID: work2.ID, Views: 10, Likes: 100, Comments: 10})
|
|
s.Require().NoError(s.App.Analytics.UpdateTrending(context.Background()))
|
|
|
|
// Act
|
|
query := `
|
|
query GetTrendingWorks {
|
|
trendingWorks {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
`
|
|
response, err := executeGraphQL[TrendingWorksResponse](s, query, nil, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL query should not return errors")
|
|
|
|
// Assert
|
|
s.Len(response.Data.TrendingWorks, 2)
|
|
s.Equal(fmt.Sprintf("%d", work2.ID), response.Data.TrendingWorks[0].ID)
|
|
})
|
|
}
|
|
|
|
func (s *GraphQLIntegrationSuite) TestCollectionMutations() {
|
|
// Create users for testing authorization
|
|
owner, ownerToken := s.CreateAuthenticatedUser("collectionowner", "owner@test.com", domain.UserRoleReader)
|
|
otherUser, otherToken := s.CreateAuthenticatedUser("otheruser", "other@test.com", domain.UserRoleReader)
|
|
_ = otherUser
|
|
|
|
var collectionID string
|
|
|
|
s.Run("should create a collection", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation CreateCollection($input: CollectionInput!) {
|
|
createCollection(input: $input) {
|
|
id
|
|
name
|
|
description
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"name": "My New Collection",
|
|
"description": "A collection of my favorite works.",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[CreateCollectionResponse](s, mutation, variables, &ownerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
s.NotNil(response.Data.CreateCollection.ID, "Collection ID should not be nil")
|
|
collectionID = response.Data.CreateCollection.ID // Save for later tests
|
|
s.Equal("My New Collection", response.Data.CreateCollection.Name, "Collection name should match")
|
|
s.Equal("A collection of my favorite works.", response.Data.CreateCollection.Description, "Collection description should match")
|
|
})
|
|
|
|
s.Run("should update a collection", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateCollection($id: ID!, $input: CollectionInput!) {
|
|
updateCollection(id: $id, input: $input) {
|
|
id
|
|
name
|
|
description
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": collectionID,
|
|
"input": map[string]interface{}{
|
|
"name": "My Updated Collection",
|
|
"description": "An updated description.",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[UpdateCollectionResponse](s, mutation, variables, &ownerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Nil(response.Errors, "GraphQL mutation should not return errors")
|
|
|
|
// Verify the response
|
|
s.Equal("My Updated Collection", response.Data.UpdateCollection.Name)
|
|
})
|
|
|
|
s.Run("should not update a collection owned by another user", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation UpdateCollection($id: ID!, $input: CollectionInput!) {
|
|
updateCollection(id: $id, input: $input) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": collectionID,
|
|
"input": map[string]interface{}{
|
|
"name": "Attempted Takeover",
|
|
},
|
|
}
|
|
|
|
// Execute the mutation with the other user's token
|
|
response, err := executeGraphQL[any](s, mutation, variables, &otherToken)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response.Errors)
|
|
})
|
|
|
|
s.Run("should add a work to a collection", func() {
|
|
// Create a work
|
|
work := s.CreateTestWork("Test Work", "en", "Test content")
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation AddWorkToCollection($collectionId: ID!, $workId: ID!) {
|
|
addWorkToCollection(collectionId: $collectionId, workId: $workId) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"collectionId": collectionID,
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[AddWorkToCollectionResponse](s, mutation, variables, &ownerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().Nil(response.Errors)
|
|
})
|
|
|
|
s.Run("should remove a work from a collection", func() {
|
|
// Create a work and add it to the collection first
|
|
work := s.CreateTestWork("Another Work", "en", "Some content")
|
|
collectionIDInt, err := strconv.ParseUint(collectionID, 10, 64)
|
|
s.Require().NoError(err)
|
|
err = s.App.Collection.Commands.AddWorkToCollection(context.Background(), collection.AddWorkToCollectionInput{
|
|
CollectionID: uint(collectionIDInt),
|
|
WorkID: work.ID,
|
|
UserID: owner.ID,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation RemoveWorkFromCollection($collectionId: ID!, $workId: ID!) {
|
|
removeWorkFromCollection(collectionId: $collectionId, workId: $workId) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"collectionId": collectionID,
|
|
"workId": fmt.Sprintf("%d", work.ID),
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[RemoveWorkFromCollectionResponse](s, mutation, variables, &ownerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().Nil(response.Errors)
|
|
})
|
|
|
|
s.Run("should delete a collection", func() {
|
|
// Define the mutation
|
|
mutation := `
|
|
mutation DeleteCollection($id: ID!) {
|
|
deleteCollection(id: $id)
|
|
}
|
|
`
|
|
|
|
// Define the variables
|
|
variables := map[string]interface{}{
|
|
"id": collectionID,
|
|
}
|
|
|
|
// Execute the mutation
|
|
response, err := executeGraphQL[any](s, mutation, variables, &ownerToken)
|
|
s.Require().NoError(err)
|
|
s.Require().Nil(response.Errors)
|
|
s.True(response.Data.(map[string]interface{})["deleteCollection"].(bool))
|
|
})
|
|
} |