drone/registry/app/api/controller/metadata/create_registry_test.go
Manjunatha EN a73113f8e6 fix: [AH-771]: gitness unit test refactoring (#3589)
* fix merge conflicts
* fix merge conflicts
* fix review comment
* fix review comment
* fix review comment
* fix: [AH-771]: gitness unit test refactoring
* fix: [AH-771]: resolved review comments
* fix: [AH-771]: resolved review comments
* fix: [AH-771] Registry test refactoring and improvements

- Refactored registry metadata test implementations
- Improved code organization and readability
- Fixed line length issues in test files
- Removed unused fields from request.go
- Added proper license headers
- Fixed linting issues in mock files
- Simplified test setup and assertions
- Updated wire generation for cmd package
- Added nolint:exhaustive directive for package type switch

fix: [AH-771] Registry test refactoring and improvements

- Refactored registry metadata test implementations
- Improved code organization and readability
- Fixed line length issues in test files
- Removed unused fields from request.go
- Added proper license headers
- Fixed linting issues in mock files
- Simplifi
2025-04-22 14:49:24 +00:00

484 lines
19 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//nolint:lll,revive // revive:disable:unused-parameter
package metadata_test
import (
"context"
"errors"
"fmt"
"testing"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/events"
"github.com/harness/gitness/registry/app/api/controller/metadata"
"github.com/harness/gitness/registry/app/api/controller/mocks"
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
registryevents "github.com/harness/gitness/registry/app/events"
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/types"
"github.com/harness/gitness/registry/utils"
coretypes "github.com/harness/gitness/types"
gitnessenum "github.com/harness/gitness/types/enum"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
const (
// TestRegistryName is the name used for test registries.
testRegistryName = "test-registry"
)
var (
// ErrTestConsumerNotNeeded is a sentinel error indicating consumer is not needed in tests.
ErrTestConsumerNotNeeded = errors.New("consumer not needed in tests")
)
func TestCreateRegistry(t *testing.T) {
space := &coretypes.SpaceCore{
ID: 1,
Path: "root",
}
// Create mock event system components.
producer := &mocks.StreamProducer{}
// Set up producer expectations.
producer.On("Send", mock.Anything, "registry", mock.MatchedBy(func(payload map[string]interface{}) bool {
return payload["action"] == "registry.create" &&
payload["registry_id"] == int64(1) &&
payload["registry_name"] == testRegistryName
})).Return("", nil).Once()
// Create events system.
consumerFactory := func(_ string, _ string) (events.StreamConsumer, error) {
// Return a sentinel error to indicate consumer is not needed in tests.
return nil, ErrTestConsumerNotNeeded
}
eventsSystem, err := events.NewSystem(consumerFactory, producer)
if err != nil {
t.Fatalf("Failed to create events system: %v", err)
}
// Create event reporter.
reporter, err := registryevents.NewReporter(eventsSystem)
if err != nil {
t.Fatalf("Failed to create event reporter: %v", err)
}
eventReporter := *reporter // Use value instead of pointer.
// Helper mocks and setup complete.
tests := []struct {
name string
request api.CreateRegistryRequestObject
setupMocks func() *metadata.APIController
expectedResp interface{}
}{
{
name: "create_virtual_registry_success",
request: api.CreateRegistryRequestObject{
Body: &api.CreateRegistryJSONRequestBody{
Identifier: testRegistryName,
ParentRef: utils.StringPtr("root"),
Config: func() *api.RegistryConfig {
config := &api.RegistryConfig{Type: api.RegistryTypeVIRTUAL}
_ = config.FromVirtualConfig(api.VirtualConfig{UpstreamProxies: &[]string{}})
return config
}(),
PackageType: api.PackageTypeDOCKER,
CleanupPolicy: &[]api.CleanupPolicy{},
},
},
expectedResp: api.CreateRegistry201JSONResponse{
RegistryResponseJSONResponse: api.RegistryResponseJSONResponse{
Data: api.Registry{
Identifier: testRegistryName,
Config: &api.RegistryConfig{
Type: api.RegistryTypeVIRTUAL,
},
PackageType: api.PackageTypeDOCKER,
Url: "http://example.com/registry/test-registry",
Description: utils.StringPtr(""),
CreatedAt: utils.StringPtr("-62135596800000"),
ModifiedAt: utils.StringPtr("-62135596800000"),
},
Status: api.StatusSUCCESS,
},
},
setupMocks: func() *metadata.APIController {
mockRegistryMetadataHelper := new(mocks.RegistryMetadataHelper)
mockRegistryRepo := new(mocks.RegistryRepository)
mockSpaceFinder := new(mocks.SpaceFinder)
mockAuthorizer := new(mocks.Authorizer)
mockAuditService := new(mocks.AuditService)
mockCleanupPolicyRepo := new(mocks.CleanupPolicyRepository)
mockTransactor := new(mocks.Transactor)
mockGenericBlobRepo := new(mocks.GenericBlobRepository)
// Create a mock URL provider.
mockURLProvider := new(mocks.Provider)
// Set up common URL provider expectations
mockURLProvider.On("PackageURL", mock.Anything, mock.Anything, mock.Anything,
mock.Anything).Return("http://example.com/registry/test-registry/docker")
mockURLProvider.On("GenerateUIRegistryURL", mock.Anything, mock.Anything,
mock.Anything).Return("http://example.com/registry/test-registry")
mockURLProvider.On("RegistryURL", mock.Anything, mock.Anything,
mock.Anything).Return("http://example.com/registry/test-registry")
// Setup base info mock.
baseInfo := &types.RegistryRequestBaseInfo{
RegistryID: 1,
RegistryIdentifier: testRegistryName,
ParentRef: "root",
ParentID: 2,
RootIdentifierID: 3,
RootIdentifier: "root",
RegistryType: api.RegistryTypeVIRTUAL,
PackageType: api.PackageTypeDOCKER,
}
// Create the registry entity that will be used in mocks.
registry := &types.Registry{
ID: baseInfo.RegistryID,
Name: testRegistryName,
ParentID: baseInfo.ParentID,
RootParentID: baseInfo.RootIdentifierID,
Type: api.RegistryTypeVIRTUAL,
PackageType: api.PackageTypeDOCKER,
}
// 1. Mock the initial registry metadata lookup.
mockRegistryMetadataHelper.On("GetRegistryRequestBaseInfo", mock.Anything, "root", "").Return(baseInfo, nil).Once()
// 2. Mock the space lookup.
mockSpaceFinder.On("FindByRef", mock.Anything, "root").Return(space, nil).Once()
// 3. Mock the authorization check.
mockAuthorizer.On("Check", mock.Anything, mock.Anything,
mock.MatchedBy(func(scope *coretypes.Scope) bool { return scope.SpacePath == "root" }),
mock.MatchedBy(func(r *coretypes.Resource) bool { return r.Type == gitnessenum.ResourceTypeRegistry }),
gitnessenum.PermissionRegistryEdit).Return(true, nil).Once()
// 4. Mock registry creation
mockRegistryRepo.On("Create", mock.Anything,
mock.MatchedBy(func(r *types.Registry) bool {
return r.Name == testRegistryName &&
r.ParentID == baseInfo.ParentID &&
r.Type == api.RegistryTypeVIRTUAL &&
r.PackageType == api.PackageTypeDOCKER
})).Return(baseInfo.RegistryID, nil).Once()
// 5. Mock registry retrieval.
mockRegistryRepo.On("Get", mock.Anything, baseInfo.RegistryID).Return(registry, nil).Once()
// 6. Mock cleanup policy retrieval.
mockCleanupPolicyRepo.On("GetByRegistryID", mock.Anything,
baseInfo.RegistryID).Return(&[]types.CleanupPolicy{}, nil).Once()
// URL provider mock expectations set up above
// Mock setup already done above.
// Setup already covered above.
// Create file manager.
var app = &filemanager.App{
Context: context.Background(),
}
fileManager := filemanager.NewFileManager(
app,
mockRegistryRepo,
mockGenericBlobRepo,
nil, // nodesRepo - not needed for this test.
mockTransactor,
nil, // reporter - not needed for this test.
)
// Setup audit service mock.
mockAuditService.On("Log", mock.Anything,
mock.MatchedBy(func(p coretypes.Principal) bool { return p.ID == 1 && p.Type == "user" }),
mock.MatchedBy(func(r audit.Resource) bool {
return r.Type == audit.ResourceTypeRegistry && r.Identifier == testRegistryName
}),
audit.ActionCreated,
"root",
mock.Anything,
).Return(nil).Once()
// Setup registry repo mock.
mockRegistryRepo.On("FetchUpstreamProxyKeys", mock.Anything, mock.Anything).Return([]string{}, nil).Once()
mockCleanupPolicyRepo.On("GetByRegistryID", mock.Anything,
baseInfo.RegistryID).Return(&[]types.CleanupPolicy{}, nil).Once()
// Authorizer mock already setup above.
// Create controller.
// Setup transactor mock.
mockTransactor.On("WithTx", mock.Anything,
mock.AnythingOfType("func(context.Context) error"), mock.Anything).Run(func(args mock.Arguments) {
// Execute the transaction function.
txFn, ok := args.Get(1).(func(context.Context) error)
assert.True(t, ok, "Transaction function conversion failed")
err := txFn(context.Background())
// Check if an error occurs during transaction execution.
assert.NoError(t, err, "Transaction function should not return an error")
}).Return(nil)
// Create controller.
return metadata.NewAPIController(
mockRegistryRepo,
fileManager,
nil, // blobStore.
nil, // genericBlobStore.
nil, // upstreamProxyStore.
nil, // tagStore.
nil, // manifestStore.
mockCleanupPolicyRepo,
nil, // imageStore.
nil, // driver.
mockSpaceFinder,
mockTransactor,
mockURLProvider,
mockAuthorizer,
mockAuditService,
nil, // artifactStore.
nil, // webhooksRepository.
nil, // webhooksExecutionRepository.
mockRegistryMetadataHelper,
nil, // webhookService.
eventReporter,
nil, // downloadStatRepository.
)
},
},
{
name: "create_registry_invalid_parent",
request: api.CreateRegistryRequestObject{
Body: &api.CreateRegistryJSONRequestBody{
Identifier: testRegistryName,
ParentRef: utils.StringPtr("invalid"),
Config: func() *api.RegistryConfig {
config := &api.RegistryConfig{Type: api.RegistryTypeVIRTUAL}
_ = config.FromVirtualConfig(api.VirtualConfig{UpstreamProxies: &[]string{}})
return config
}(),
PackageType: api.PackageTypeDOCKER,
CleanupPolicy: &[]api.CleanupPolicy{},
},
},
expectedResp: api.CreateRegistry400JSONResponse{
BadRequestJSONResponse: api.BadRequestJSONResponse{
Code: "400",
Message: "space not found",
},
},
setupMocks: func() *metadata.APIController {
mockRegistryMetadataHelper := new(mocks.RegistryMetadataHelper)
mockRegistryRepo := new(mocks.RegistryRepository)
mockTransactor := new(mocks.Transactor)
mockGenericBlobRepo := new(mocks.GenericBlobRepository)
// Setup error case mock.
mockRegistryMetadataHelper.On("GetRegistryRequestBaseInfo", mock.Anything, "invalid", "").
Return(nil, fmt.Errorf("space not found")).Once()
app := &filemanager.App{
Context: context.Background(),
}
fileManager := filemanager.NewFileManager(
app,
mockRegistryRepo,
mockGenericBlobRepo,
nil, // nodesRepo - not needed for this test.
mockTransactor,
nil, // reporter - not needed for this test.
)
return metadata.NewAPIController(
mockRegistryRepo,
fileManager,
nil, // blobStore.
nil, // genericBlobStore.
nil, // upstreamProxyStore.
nil, // tagStore.
nil, // manifestStore.
nil, // cleanupPolicyStore
nil, // imageStore.
nil, // driver.
nil, // spaceFinder
mockTransactor,
nil, // urlProvider.
nil, // authorizer.
nil, // auditService.
nil, // artifactStore.
nil, // webhooksRepository.
nil, // webhooksExecutionRepository.
mockRegistryMetadataHelper,
nil, // webhookService.
eventReporter,
nil, // downloadStatRepository.
)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create the controller.
controller := tt.setupMocks()
// Create context with auth session.
ctx := context.Background()
session := &auth.Session{
Principal: coretypes.Principal{
ID: 1,
Type: "user",
},
}
ctx = request.WithAuthSession(ctx, session)
// Call the API.
registryResp, err := controller.CreateRegistry(ctx, tt.request)
// Verify response matches expected type and content.
switch expected := tt.expectedResp.(type) {
case api.CreateRegistry201JSONResponse:
assert.NoError(t, err, "Expected no error but got one")
actualResp, ok := registryResp.(api.CreateRegistry201JSONResponse)
assert.True(t, ok, "Expected 201 response")
assert.Equal(t, expected.Status, actualResp.Status, "Response status should match")
// Verify registry data fields individually.
assert.Equal(t, expected.Data.Identifier, actualResp.Data.Identifier, "Registry identifier should match")
assert.Equal(t, expected.Data.PackageType, actualResp.Data.PackageType, "Package type should match")
assert.Equal(t, expected.Data.Url, actualResp.Data.Url, "Registry URL should match")
assert.Equal(t, expected.Data.Description, actualResp.Data.Description, "Description should match")
assert.Equal(t, expected.Data.CreatedAt, actualResp.Data.CreatedAt, "CreatedAt should match")
assert.Equal(t, expected.Data.ModifiedAt, actualResp.Data.ModifiedAt, "ModifiedAt should match")
// Verify config type.
assert.NotNil(t, actualResp.Data.Config, "Config should not be nil")
assert.Equal(t, expected.Data.Config.Type, actualResp.Data.Config.Type, "Config type should match")
// Verify mock expectations for success case.
called := controller.RegistryRepository.(*mocks.RegistryRepository).AssertCalled(t, "Create", //nolint:errcheck
mock.Anything, mock.MatchedBy(func(r *types.Registry) bool {
return r.Name == tt.request.Body.Identifier &&
r.Type == tt.request.Body.Config.Type &&
r.PackageType == tt.request.Body.PackageType
}))
assert.True(t, called, "Expected Create call not made")
// Verify all mocks expectations were met.
mockRegistry, regOk := controller.RegistryRepository.(*mocks.RegistryRepository)
assert.True(t, regOk, "Type assertion to RegistryRepository failed")
regAssertOk := mockRegistry.AssertExpectations(t)
assert.True(t, regAssertOk, "Mock expectations failed for RegistryRepository")
mockAudit, auditOk := controller.AuditService.(*mocks.AuditService)
assert.True(t, auditOk, "Type assertion to AuditService failed")
auditAssertOk := mockAudit.AssertExpectations(t)
assert.True(t, auditAssertOk, "Mock expectations failed for AuditService")
// Verify audit expectations.
logCalled := controller.AuditService.(*mocks.AuditService).AssertCalled(t, "Log", mock.Anything, //nolint:errcheck
mock.Anything, mock.Anything, audit.ActionCreated, mock.Anything, mock.Anything)
assert.True(t, logCalled, "Expected Log call not made")
case api.CreateRegistry400JSONResponse:
assert.Error(t, err, "Expected an error")
actualResp, ok := registryResp.(api.CreateRegistry400JSONResponse)
assert.True(t, ok, "Expected 400 response")
assert.Equal(t, expected.Code, actualResp.Code, "Error code should match")
assert.Equal(t, expected.Message, actualResp.Message, "Error message should match")
// Additional assertions for specific error cases.
switch tt.name {
case "create_registry_invalid_parent":
assert.Contains(t, actualResp.Message, "space not found",
"Error message should indicate invalid parent")
notCalled := controller.RegistryRepository.(*mocks.RegistryRepository).AssertNotCalled(t, //nolint:errcheck
"Create", mock.Anything, mock.Anything)
assert.True(t, notCalled, "Unexpected Create call made")
// Verify expectations for the error case.
metaHelper, metaHelperOk := controller.RegistryMetadataHelper.(*mocks.RegistryMetadataHelper)
assert.True(t, metaHelperOk, "Type assertion to RegistryMetadataHelper failed")
assertMetaOk := metaHelper.AssertExpectations(t)
assert.True(t, assertMetaOk, "Mock expectations failed for RegistryMetadataHelper")
case "create_registry_duplicate":
assert.Contains(t, actualResp.Message, "already defined",
"Error message should indicate duplicate registry")
assert.Contains(t, actualResp.Message, tt.request.Body.Identifier,
"Error message should include the registry identifier")
getByNameCalled := controller.RegistryRepository.(*mocks.RegistryRepository).AssertCalled(t, //nolint:errcheck
"GetByRootParentIDAndName", mock.Anything, mock.Anything, tt.request.Body.Identifier)
assert.True(t, getByNameCalled, "Expected GetByRootParentIDAndName call not made")
}
case api.CreateRegistry403JSONResponse:
assert.Error(t, err, "Expected an error")
actualResp, ok := registryResp.(api.CreateRegistry403JSONResponse)
assert.True(t, ok, "Expected 403 response")
assert.Equal(t, expected.Code, actualResp.Code, "Error code should match")
assert.Equal(t, expected.Message, actualResp.Message, "Error message should match")
assert.Contains(t, actualResp.Message, "unauthorized",
"Error message should indicate authorization failure")
_ = controller.RegistryRepository.(*mocks.RegistryRepository).AssertNotCalled(t, //nolint:errcheck
"Create", mock.Anything, mock.Anything)
default:
t.Fatalf("Unexpected response type: %T", tt.expectedResp)
}
// Verify common mock expectations.
if controller.RegistryRepository != nil {
registryRepo, regOk := controller.RegistryRepository.(*mocks.RegistryRepository)
assert.True(t, regOk, "Type assertion to RegistryRepository failed")
assertRegOk := registryRepo.AssertExpectations(t)
assert.True(t, assertRegOk, "Mock expectations failed for RegistryRepository")
}
if controller.RegistryMetadataHelper != nil {
metaHelper, metaHelperOk := controller.RegistryMetadataHelper.(*mocks.RegistryMetadataHelper)
assert.True(t, metaHelperOk, "Type assertion to RegistryMetadataHelper failed")
assertMetaOk := metaHelper.AssertExpectations(t)
assert.True(t, assertMetaOk, "Mock expectations failed for RegistryMetadataHelper")
}
if controller.SpaceFinder != nil {
spaceFinder, finderOk := controller.SpaceFinder.(*mocks.SpaceFinder)
assert.True(t, finderOk, "Type assertion to SpaceFinder failed")
spaceFinderOk := spaceFinder.AssertExpectations(t)
assert.True(t, spaceFinderOk, "Mock expectations failed for SpaceFinder")
}
if controller.Authorizer != nil {
auth, authOk := controller.Authorizer.(*mocks.Authorizer)
assert.True(t, authOk, "Type assertion to Authorizer failed")
authAssertOk := auth.AssertExpectations(t)
assert.True(t, authAssertOk, "Mock expectations failed for Authorizer")
}
if controller.AuditService != nil {
auditSvc, auditSvcOk := controller.AuditService.(*mocks.AuditService)
assert.True(t, auditSvcOk, "Type assertion to AuditService failed")
auditSvcAssertOk := auditSvc.AssertExpectations(t)
assert.True(t, auditSvcAssertOk, "Mock expectations failed for AuditService")
}
})
}
}