Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add unit tests for Translations API methods #26

Merged
merged 3 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions crowdin/model/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package model

import (
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

func TestErrorResponse_Error(t *testing.T) {
err := &ErrorResponse{
Response: &http.Response{},
Err: Error{
Code: 404,
Message: "Resource Not Found",
},
}

assert.Equal(t, "404 Resource Not Found", err.Error())
assert.NotNil(t, err.Response)
}

func TestValidationErrorResponse_Error(t *testing.T) {
err := &ValidationErrorResponse{
Response: &http.Response{},
Errors: []ValidationError{
{
Error: struct {
Key string `json:"key"`
Errors []struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"errors"`
}{
Key: "name",
Errors: []struct {
Code string `json:"code"`
Message string `json:"message"`
}{
{
Code: "isEmpty",
Message: "Value is required and can't be empty",
},
},
},
},
{
Error: struct {
Key string `json:"key"`
Errors []struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"errors"`
}{
Key: "sourceLanguage",
Errors: []struct {
Code string `json:"code"`
Message string `json:"message"`
}{
{
Code: "required",
Message: "Field is required",
},
{
Code: "notFound",
Message: "Field not found",
},
},
},
},
},
Status: http.StatusBadRequest,
}

expected := "name: Value is required and can't be empty (isEmpty); sourceLanguage: Field is required (required), Field not found (notFound)"
result := err.Error()

assert.Equal(t, expected, result)
assert.Equal(t, http.StatusBadRequest, err.Status)
assert.NotNil(t, err.Response)
}

func TestValidationErrorResponse_Error_SingleError(t *testing.T) {
err := &ValidationErrorResponse{
Response: &http.Response{},
Errors: []ValidationError{
{
Error: struct {
Key string `json:"key"`
Errors []struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"errors"`
}{
Key: "name",
Errors: []struct {
Code string `json:"code"`
Message string `json:"message"`
}{
{
Code: "required",
Message: "name is required",
},
},
},
},
},
Status: http.StatusBadRequest,
}

expected := "name: name is required (required)"
result := err.Error()

assert.Equal(t, expected, result)
assert.Equal(t, http.StatusBadRequest, err.Status)
assert.NotNil(t, err.Response)
}

func TestValidationErrorResponse_Error_EmptyErrors(t *testing.T) {
response := &ValidationErrorResponse{
Errors: []ValidationError{},
}

assert.Equal(t, "", response.Error())
}
145 changes: 102 additions & 43 deletions crowdin/model/translations.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type (

PreTranslationAttributes struct {
LanguageIDs []string `json:"languageIds"`
FileIDs []int `json:"fileIds"`
BranchIDs []int `json:"branchIds,omitempty"`
FileIDs []int `json:"fileIds,omitempty"`
Method *string `json:"method,omitempty"`
AutoApproveOption *string `json:"autoApproveOption,omitempty"`
DuplicateTranslations *bool `json:"duplicateTranslations,omitempty"`
Expand All @@ -43,18 +44,18 @@ type PreTranslationRequest struct {
// Files array that should be translated.
FileIDs []int `json:"fileIds"`
// Defines pre-translation method. Enum: "tm", "mt". Default: "tm".
// - tm – pre-translation via Translation Memory.
// - mt – pre-translation via Machine Translation. "mt" should be used with `engineId` parameter.
// - tm – pre-translation via Translation Memory.
// - mt – pre-translation via Machine Translation. "mt" should be used with `engineId` parameter.
Method string `json:"method,omitempty"`
// Machine Translation engine Identifier. Required if `method` is set to "mt".
EngineID int `json:"engineId,omitempty"`
// Defines which translations added by TM pre-translation should be auto-approved. Default: "none".
// Defines which translations added by TM pre-translation should be auto-approved. Default: "none".
// Enum: "all", "exceptAutoSubstituted", "perfectMatchApprovedOnly", "perfectMatchOnly", "none"
// - all – all
// - perfectMatchOnly – with perfect TM match
// - exceptAutoSubstituted – all (skip auto-substituted suggestions)
// - perfectMatchApprovedOnly - with perfect TM match (approved previously)
// - none – no auto-approve
// - all – all
// - perfectMatchOnly – with perfect TM match
// - exceptAutoSubstituted – all (skip auto-substituted suggestions)
// - perfectMatchApprovedOnly - with perfect TM match (approved previously)
// - none – no auto-approve
AutoApproveOption string `json:"autoApproveOption,omitempty"`
// Adds translations even if the same translation already exists. Default is false.
// Note: Works only with TM pre-translation method.
Expand All @@ -70,7 +71,9 @@ type PreTranslationRequest struct {
// Note: Works only with TM pre-translation method.
TranslateWithPerfectMatchOnly *bool `json:"translateWithPerfectMatchOnly,omitempty"`
// Defines fallback languages mapping. The passed value should contain a map of
// language ID as a key and an array of fallback language IDs as a value.
// languageID as a key and an array of fallback language IDs as a value.
// - languageID – Crowdin ID for the specified language.
// - []string – an array containing fallback language IDs.
// Note: Available only for TM Pre-Translation.
FallbackLanguages map[string][]string `json:"fallbackLanguages,omitempty"`
// Label Identifiers.
Expand Down Expand Up @@ -104,12 +107,21 @@ type BuildProjectDirectoryTranslationRequest struct {
// Note: true value can't be used with `skipUntranslatedFiles=true` in same request .
SkipUntranslatedStrings *bool `json:"skipUntranslatedStrings,omitempty"`
// Defines whether to export only translated file. Default: false.
// Note: true value can't be used with `skipUntranslatedStrings=true`` in same request.
// Note: true value can't be used with `skipUntranslatedStrings=true` in same request.
SkipUntranslatedFiles *bool `json:"skipUntranslatedFiles,omitempty"`
// Defines whether to export only approved strings. Default: false.
ExportApprovedOnly *bool `json:"exportApprovedOnly,omitempty"`
// Preserve folder hierarchy. Default: false.
PreserveFolderHierarchy *bool `json:"preserveFolderHierarchy,omitempty"`

// Defines whether to export only approved strings.
// Note: value greater than 0 can't be used with `exportStringsThatPassedWorkflow=true`
// in same request.
ExportWithMinApprovalsCount *int `json:"exportWithMinApprovalsCount,omitempty"`
// Defines whether to export only strings that passed workflow.
// Note: true value can't be used with `exportWithMinApprovalsCount>0` in same request
// or in projects without an assigned workflow.
ExportStringsThatPassedWorkflow *bool `json:"exportStringsThatPassedWorkflow,omitempty"`
}

// Validate checks if the build project directory translation request is valid.
Expand All @@ -123,6 +135,11 @@ func (r *BuildProjectDirectoryTranslationRequest) Validate() error {
(*r.SkipUntranslatedStrings && *r.SkipUntranslatedFiles) {
return fmt.Errorf("skipUntranslatedStrings and skipUntranslatedFiles must not be true at the same request")
}
if (r.ExportWithMinApprovalsCount != nil && r.ExportStringsThatPassedWorkflow != nil) &&
(*r.ExportWithMinApprovalsCount > 0 && *r.ExportStringsThatPassedWorkflow) {
return fmt.Errorf("exportWithMinApprovalsCount and exportStringsThatPassedWorkflow must not be true at the same request")
}

return nil
}

Expand All @@ -146,10 +163,19 @@ type BuildProjectFileTranslationRequest struct {
// Note: true value can't be used with `skipUntranslatedFiles=true`` in same request.
SkipUntranslatedStrings *bool `json:"skipUntranslatedStrings,omitempty"`
// Defines whether to export only translated file. Default: false.
// Note: true value can't be used with skipUntranslatedStrings=true in same request.
// Note: true value can't be used with `skipUntranslatedStrings=true` in same request.
SkipUntranslatedFiles *bool `json:"skipUntranslatedFiles,omitempty"`
// Defines whether to export only approved strings. Default: false.
ExportApprovedOnly *bool `json:"exportApprovedOnly,omitempty"`

// Defines whether to export only approved strings.
// Note: value greater than 0 can't be used with `exportStringsThatPassedWorkflow=true`
// in same request.
ExportWithMinApprovalsCount *int `json:"exportWithMinApprovalsCount,omitempty"`
// Defines whether to export only strings that passed workflow.
// Note: true value can't be used with `exportWithMinApprovalsCount>0` in same request
// or in projects without an assigned workflow.
ExportStringsThatPassedWorkflow *bool `json:"exportStringsThatPassedWorkflow,omitempty"`
}

// Validate checks if the build project file translation request is valid.
Expand All @@ -166,6 +192,11 @@ func (r *BuildProjectFileTranslationRequest) Validate() error {
(*r.SkipUntranslatedStrings && *r.SkipUntranslatedFiles) {
return fmt.Errorf("skipUntranslatedStrings and skipUntranslatedFiles must not be true at the same request")
}
if (r.ExportWithMinApprovalsCount != nil && r.ExportStringsThatPassedWorkflow != nil) &&
(*r.ExportWithMinApprovalsCount > 0 && *r.ExportStringsThatPassedWorkflow) {
return fmt.Errorf("exportWithMinApprovalsCount and exportStringsThatPassedWorkflow must not be true at the same request")
}

return nil
}

Expand Down Expand Up @@ -194,21 +225,32 @@ func (o *TranslationsBuildsListOptions) Values() (url.Values, bool) {

// TranslationsProjectBuild represents a project build.
type TranslationsProjectBuild struct {
ID int `json:"id"`
ProjectID int `json:"projectId"`
Status string `json:"status"`
Progress int `json:"progress"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
FinishedAt string `json:"finishedAt,omitempty"`
Attributes struct {
BranchID *int `json:"branchId,omitempty"`
DirectoryID *int `json:"directoryId,omitempty"`
TargetLanguageIDs []string `json:"targetLanguageIds,omitempty"`
SkipUntranslatedStrings bool `json:"skipUntranslatedStrings,omitempty"`
SkipUntranslatedFiles bool `json:"skipUntranslatedFiles,omitempty"`
ExportApprovedOnly bool `json:"exportApprovedOnly,omitempty"`
} `json:"attributes,omitempty"`
ID int `json:"id"`
ProjectID int `json:"projectId"`
Status string `json:"status"`
Progress int `json:"progress"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
FinishedAt string `json:"finishedAt,omitempty"`
Attributes *BuildAttributes `json:"attributes,omitempty"`
}

// BuildAttributes represents the attributes of a project build.
type BuildAttributes struct {
BranchID *int `json:"branchId,omitempty"`
DirectoryID *int `json:"directoryId,omitempty"`
TargetLanguageIDs []string `json:"targetLanguageIds,omitempty"`
SkipUntranslatedStrings *bool `json:"skipUntranslatedStrings,omitempty"`
SkipUntranslatedFiles *bool `json:"skipUntranslatedFiles,omitempty"`
ExportApprovedOnly *bool `json:"exportApprovedOnly,omitempty"`
ExportWithMinApprovalsCount *int `json:"exportWithMinApprovalsCount,omitempty"`
ExportStringsThatPassedWorkflow *bool `json:"exportStringsThatPassedWorkflow,omitempty"`

Pseudo *bool `json:"pseudo,omitempty"`
Prefix *string `json:"prefix,omitempty"`
Suffix *string `json:"suffix,omitempty"`
LengthTransformation *int `json:"lengthTransformation,omitempty"`
CharTransformation *string `json:"charTransformation,omitempty"`
}

// TranslationsProjectBuildResponse defines the structure of a response when
Expand All @@ -221,7 +263,6 @@ type TranslationsProjectBuildResponse struct {
// getting a list of project builds.
type TranslationsProjectBuildsListResponse struct {
Data []*TranslationsProjectBuildResponse `json:"data"`
Pagination *Pagination `json:"pagination"`
}

type (
Expand All @@ -246,6 +287,15 @@ type (
SkipUntranslatedFiles *bool `json:"skipUntranslatedFiles,omitempty"`
// Defines whether to export only approved strings.
ExportApprovedOnly *bool `json:"exportApprovedOnly,omitempty"`

// Defines whether to export only approved strings.
// Note: value greater than 0 can't be used with `exportStringsThatPassedWorkflow=true`
// in same request.
ExportWithMinApprovalsCount *int `json:"exportWithMinApprovalsCount,omitempty"`
// Defines whether to export only strings that passed workflow.
// Note: true value can't be used with `exportWithMinApprovalsCount>0` in same request
// or in projects without an assigned workflow.
ExportStringsThatPassedWorkflow *bool `json:"exportStringsThatPassedWorkflow,omitempty"`
}

// PsuedoBuildProjectRequest defines the structure of a request to build a project
Expand All @@ -270,16 +320,26 @@ type (
}
)

// ValidateBuildRequest implements the BuildProjectTranslationRequest interface.
func (r *BuildProjectRequest) ValidateBuildRequest() error {
// Validate checks if the build project request is valid.
// It implements the crowdin.RequestValidator interface.
func (r *BuildProjectRequest) Validate() error {
if r == nil {
return ErrNilRequest
}
return r.ValidateBuildRequest()
}

// ValidateBuildRequest implements the BuildProjectTranslationRequest interface.
func (r *BuildProjectRequest) ValidateBuildRequest() error {
if (r.SkipUntranslatedStrings != nil && r.SkipUntranslatedFiles != nil) &&
(*r.SkipUntranslatedStrings && *r.SkipUntranslatedFiles) {
return fmt.Errorf("`skipUntranslatedStrings` and `skipUntranslatedFiles` must not be true at the same request")
}
if (r.ExportWithMinApprovalsCount != nil && r.ExportStringsThatPassedWorkflow != nil) &&
(*r.ExportWithMinApprovalsCount > 0 && *r.ExportStringsThatPassedWorkflow) {
return fmt.Errorf("`exportWithMinApprovalsCount` and `exportStringsThatPassedWorkflow` must not be true at the same request")
}

return nil
}

Expand All @@ -294,26 +354,13 @@ func (r *PseudoBuildProjectRequest) Validate() error {

// PseudoBuildProjectRequest implements the BuildProjectTranslationRequest interface.
func (r *PseudoBuildProjectRequest) ValidateBuildRequest() error {
if r == nil {
return ErrNilRequest
}

if r.LengthTransformation != nil &&
(*r.LengthTransformation < -50 || *r.LengthTransformation > 100) {
return fmt.Errorf("lengthTransformation must be from -50 to 100")
}
return nil
}

// Validate checks if the build project request is valid.
// It implements the crowdin.RequestValidator interface.
func (r *BuildProjectRequest) Validate() error {
if r == nil {
return ErrNilRequest
}
return r.ValidateBuildRequest()
}

// UploadTranslationsRequest defines the structure of a request to upload translations.
type UploadTranslationsRequest struct {
// Storage Identifier.
Expand Down Expand Up @@ -388,8 +435,20 @@ type ExportTranslationRequest struct {
// Defines whether to export only translated strings. Default is false.
// Note: Can't be used with `skipUntranslatedFiles` in same request.
SkipUntranslatedStrings *bool `json:"skipUntranslatedStrings,omitempty"`
// Defines whether to export only translated file. Default is false.
// Note: Can't be used with `skipUntranslatedStrings` in same request.
SkipUntranslatedFiles *bool `json:"skipUntranslatedFiles,omitempty"`
// Defines whether to export only approved strings. Default is false.
ExportApprovedOnly *bool `json:"exportApprovedOnly,omitempty"`

// Defines whether to export only approved strings.
// Note: value greater than 0 can't be used with `exportStringsThatPassedWorkflow=true`
// in same request.
ExportWithMinApprovalsCount *int `json:"exportWithMinApprovalsCount,omitempty"`
// Defines whether to export only strings that passed workflow.
// Note: true value can't be used with `exportWithMinApprovalsCount>0` in same request
// or in projects without an assigned workflow.
ExportStringsThatPassedWorkflow *bool `json:"exportStringsThatPassedWorkflow,omitempty"`
}

// Validate checks if the request is valid.
Expand Down
Loading