// Package tests provides common helpers and mocks used in PocketBase application tests. package tests import ( "io" "os" "path" "path/filepath" "runtime" "sync" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/hook" _ "github.com/pocketbase/pocketbase/migrations" ) // TestApp is a wrapper app instance used for testing. type TestApp struct { *core.BaseApp mux sync.Mutex // EventCalls defines a map to inspect which app events // (and how many times) were triggered. EventCalls map[string]int TestMailer *TestMailer } // Cleanup resets the test application state and removes the test // app's dataDir from the filesystem. // // After this call, the app instance shouldn't be used anymore. func (t *TestApp) Cleanup() { event := new(core.TerminateEvent) event.App = t t.OnTerminate().Trigger(event, func(e *core.TerminateEvent) error { t.TestMailer.Reset() t.ResetEventCalls() t.ResetBootstrapState() return e.Next() }) if t.DataDir() != "" { os.RemoveAll(t.DataDir()) } } // ResetEventCalls resets the EventCalls counter. func (t *TestApp) ResetEventCalls() { t.mux.Lock() defer t.mux.Unlock() t.EventCalls = make(map[string]int) } func (t *TestApp) registerEventCall(name string) { t.mux.Lock() defer t.mux.Unlock() if t.EventCalls == nil { t.EventCalls = make(map[string]int) } t.EventCalls[name]++ } // NewTestApp creates and initializes a test application instance. // // It is the caller's responsibility to call `app.Cleanup()` // when the app is no longer needed. func NewTestApp(optTestDataDir ...string) (*TestApp, error) { var testDataDir string if len(optTestDataDir) == 0 || optTestDataDir[0] == "" { // fallback to the default test data directory _, currentFile, _, _ := runtime.Caller(0) testDataDir = filepath.Join(path.Dir(currentFile), "data") } else { testDataDir = optTestDataDir[0] } tempDir, err := TempDirClone(testDataDir) if err != nil { return nil, err } app := core.NewBaseApp(core.BaseAppConfig{ DataDir: tempDir, EncryptionEnv: "pb_test_env", }) // load data dir and db connections if err := app.Bootstrap(); err != nil { return nil, err } // ensure that the Dao and DB configurations are properly loaded if _, err := app.DB().NewQuery("Select 1").Execute(); err != nil { return nil, err } if _, err := app.AuxDB().NewQuery("Select 1").Execute(); err != nil { return nil, err } // apply any missing migrations if err := app.RunAllMigrations(); err != nil { return nil, err } // force disable request logs because the logs db call execute in a separate // go routine and it is possible to panic due to earlier api test completion. app.Settings().Logs.MaxDays = 0 t := &TestApp{ BaseApp: app, EventCalls: make(map[string]int), TestMailer: &TestMailer{}, } t.OnBootstrap().Bind(&hook.Handler[*core.BootstrapEvent]{ Func: func(e *core.BootstrapEvent) error { t.registerEventCall("OnBootstrap") return e.Next() }, Priority: -99999, }) t.OnServe().Bind(&hook.Handler[*core.ServeEvent]{ Func: func(e *core.ServeEvent) error { t.registerEventCall("OnServe") return e.Next() }, Priority: -99999, }) t.OnTerminate().Bind(&hook.Handler[*core.TerminateEvent]{ Func: func(e *core.TerminateEvent) error { t.registerEventCall("OnTerminate") return e.Next() }, Priority: -99999, }) t.OnBackupCreate().Bind(&hook.Handler[*core.BackupEvent]{ Func: func(e *core.BackupEvent) error { t.registerEventCall("OnBackupCreate") return e.Next() }, Priority: -99999, }) t.OnBackupRestore().Bind(&hook.Handler[*core.BackupEvent]{ Func: func(e *core.BackupEvent) error { t.registerEventCall("OnBackupRestore") return e.Next() }, Priority: -99999, }) t.OnModelCreate().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelCreate") return e.Next() }, Priority: -99999, }) t.OnModelCreateExecute().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelCreateExecute") return e.Next() }, Priority: -99999, }) t.OnModelAfterCreateSuccess().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelAfterCreateSuccess") return e.Next() }, Priority: -99999, }) t.OnModelAfterCreateError().Bind(&hook.Handler[*core.ModelErrorEvent]{ Func: func(e *core.ModelErrorEvent) error { t.registerEventCall("OnModelAfterCreateError") return e.Next() }, Priority: -99999, }) t.OnModelUpdate().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelUpdate") return e.Next() }, Priority: -99999, }) t.OnModelUpdateExecute().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelUpdateExecute") return e.Next() }, Priority: -99999, }) t.OnModelAfterUpdateSuccess().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelAfterUpdateSuccess") return e.Next() }, Priority: -99999, }) t.OnModelAfterUpdateError().Bind(&hook.Handler[*core.ModelErrorEvent]{ Func: func(e *core.ModelErrorEvent) error { t.registerEventCall("OnModelAfterUpdateError") return e.Next() }, Priority: -99999, }) t.OnModelValidate().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelValidate") return e.Next() }, Priority: -99999, }) t.OnModelDelete().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelDelete") return e.Next() }, Priority: -99999, }) t.OnModelDeleteExecute().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelDeleteExecute") return e.Next() }, Priority: -99999, }) t.OnModelAfterDeleteSuccess().Bind(&hook.Handler[*core.ModelEvent]{ Func: func(e *core.ModelEvent) error { t.registerEventCall("OnModelAfterDeleteSuccess") return e.Next() }, Priority: -99999, }) t.OnModelAfterDeleteError().Bind(&hook.Handler[*core.ModelErrorEvent]{ Func: func(e *core.ModelErrorEvent) error { t.registerEventCall("OnModelAfterDeleteError") return e.Next() }, Priority: -99999, }) t.OnRecordEnrich().Bind(&hook.Handler[*core.RecordEnrichEvent]{ Func: func(e *core.RecordEnrichEvent) error { t.registerEventCall("OnRecordEnrich") return e.Next() }, Priority: -99999, }) t.OnRecordValidate().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordValidate") return e.Next() }, Priority: -99999, }) t.OnRecordCreate().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordCreate") return e.Next() }, Priority: -99999, }) t.OnRecordCreateExecute().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordCreateExecute") return e.Next() }, Priority: -99999, }) t.OnRecordAfterCreateSuccess().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordAfterCreateSuccess") return e.Next() }, Priority: -99999, }) t.OnRecordAfterCreateError().Bind(&hook.Handler[*core.RecordErrorEvent]{ Func: func(e *core.RecordErrorEvent) error { t.registerEventCall("OnRecordAfterCreateError") return e.Next() }, Priority: -99999, }) t.OnRecordUpdate().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordUpdate") return e.Next() }, Priority: -99999, }) t.OnRecordUpdateExecute().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordUpdateExecute") return e.Next() }, Priority: -99999, }) t.OnRecordAfterUpdateSuccess().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordAfterUpdateSuccess") return e.Next() }, Priority: -99999, }) t.OnRecordAfterUpdateError().Bind(&hook.Handler[*core.RecordErrorEvent]{ Func: func(e *core.RecordErrorEvent) error { t.registerEventCall("OnRecordAfterUpdateError") return e.Next() }, Priority: -99999, }) t.OnRecordDelete().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordDelete") return e.Next() }, Priority: -99999, }) t.OnRecordDeleteExecute().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordDeleteExecute") return e.Next() }, Priority: -99999, }) t.OnRecordAfterDeleteSuccess().Bind(&hook.Handler[*core.RecordEvent]{ Func: func(e *core.RecordEvent) error { t.registerEventCall("OnRecordAfterDeleteSuccess") return e.Next() }, Priority: -99999, }) t.OnRecordAfterDeleteError().Bind(&hook.Handler[*core.RecordErrorEvent]{ Func: func(e *core.RecordErrorEvent) error { t.registerEventCall("OnRecordAfterDeleteError") return e.Next() }, Priority: -99999, }) t.OnCollectionValidate().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionValidate") return e.Next() }, Priority: -99999, }) t.OnCollectionCreate().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionCreate") return e.Next() }, Priority: -99999, }) t.OnCollectionCreateExecute().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionCreateExecute") return e.Next() }, Priority: -99999, }) t.OnCollectionAfterCreateSuccess().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionAfterCreateSuccess") return e.Next() }, Priority: -99999, }) t.OnCollectionAfterCreateError().Bind(&hook.Handler[*core.CollectionErrorEvent]{ Func: func(e *core.CollectionErrorEvent) error { t.registerEventCall("OnCollectionAfterCreateError") return e.Next() }, Priority: -99999, }) t.OnCollectionUpdate().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionUpdate") return e.Next() }, Priority: -99999, }) t.OnCollectionUpdateExecute().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionUpdateExecute") return e.Next() }, Priority: -99999, }) t.OnCollectionAfterUpdateSuccess().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionAfterUpdateSuccess") return e.Next() }, Priority: -99999, }) t.OnCollectionAfterUpdateError().Bind(&hook.Handler[*core.CollectionErrorEvent]{ Func: func(e *core.CollectionErrorEvent) error { t.registerEventCall("OnCollectionAfterUpdateError") return e.Next() }, Priority: -99999, }) t.OnCollectionDelete().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionDelete") return e.Next() }, Priority: -99999, }) t.OnCollectionDeleteExecute().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionDeleteExecute") return e.Next() }, Priority: -99999, }) t.OnCollectionAfterDeleteSuccess().Bind(&hook.Handler[*core.CollectionEvent]{ Func: func(e *core.CollectionEvent) error { t.registerEventCall("OnCollectionAfterDeleteSuccess") return e.Next() }, Priority: -99999, }) t.OnCollectionAfterDeleteError().Bind(&hook.Handler[*core.CollectionErrorEvent]{ Func: func(e *core.CollectionErrorEvent) error { t.registerEventCall("OnCollectionAfterDeleteError") return e.Next() }, Priority: -99999, }) t.OnMailerSend().Bind(&hook.Handler[*core.MailerEvent]{ Func: func(e *core.MailerEvent) error { if t.TestMailer == nil { t.TestMailer = &TestMailer{} } e.Mailer = t.TestMailer t.registerEventCall("OnMailerSend") return e.Next() }, Priority: -99999, }) t.OnMailerRecordAuthAlertSend().Bind(&hook.Handler[*core.MailerRecordEvent]{ Func: func(e *core.MailerRecordEvent) error { t.registerEventCall("OnMailerRecordAuthAlertSend") return e.Next() }, Priority: -99999, }) t.OnMailerRecordPasswordResetSend().Bind(&hook.Handler[*core.MailerRecordEvent]{ Func: func(e *core.MailerRecordEvent) error { t.registerEventCall("OnMailerRecordPasswordResetSend") return e.Next() }, Priority: -99999, }) t.OnMailerRecordVerificationSend().Bind(&hook.Handler[*core.MailerRecordEvent]{ Func: func(e *core.MailerRecordEvent) error { t.registerEventCall("OnMailerRecordVerificationSend") return e.Next() }, Priority: -99999, }) t.OnMailerRecordEmailChangeSend().Bind(&hook.Handler[*core.MailerRecordEvent]{ Func: func(e *core.MailerRecordEvent) error { t.registerEventCall("OnMailerRecordEmailChangeSend") return e.Next() }, Priority: -99999, }) t.OnMailerRecordOTPSend().Bind(&hook.Handler[*core.MailerRecordEvent]{ Func: func(e *core.MailerRecordEvent) error { t.registerEventCall("OnMailerRecordOTPSend") return e.Next() }, Priority: -99999, }) t.OnRealtimeConnectRequest().Bind(&hook.Handler[*core.RealtimeConnectRequestEvent]{ Func: func(e *core.RealtimeConnectRequestEvent) error { t.registerEventCall("OnRealtimeConnectRequest") return e.Next() }, Priority: -99999, }) t.OnRealtimeMessageSend().Bind(&hook.Handler[*core.RealtimeMessageEvent]{ Func: func(e *core.RealtimeMessageEvent) error { t.registerEventCall("OnRealtimeMessageSend") return e.Next() }, Priority: -99999, }) t.OnRealtimeSubscribeRequest().Bind(&hook.Handler[*core.RealtimeSubscribeRequestEvent]{ Func: func(e *core.RealtimeSubscribeRequestEvent) error { t.registerEventCall("OnRealtimeSubscribeRequest") return e.Next() }, Priority: -99999, }) t.OnSettingsListRequest().Bind(&hook.Handler[*core.SettingsListRequestEvent]{ Func: func(e *core.SettingsListRequestEvent) error { t.registerEventCall("OnSettingsListRequest") return e.Next() }, Priority: -99999, }) t.OnSettingsUpdateRequest().Bind(&hook.Handler[*core.SettingsUpdateRequestEvent]{ Func: func(e *core.SettingsUpdateRequestEvent) error { t.registerEventCall("OnSettingsUpdateRequest") return e.Next() }, Priority: -99999, }) t.OnSettingsReload().Bind(&hook.Handler[*core.SettingsReloadEvent]{ Func: func(e *core.SettingsReloadEvent) error { t.registerEventCall("OnSettingsReload") return e.Next() }, Priority: -99999, }) t.OnFileDownloadRequest().Bind(&hook.Handler[*core.FileDownloadRequestEvent]{ Func: func(e *core.FileDownloadRequestEvent) error { t.registerEventCall("OnFileDownloadRequest") return e.Next() }, Priority: -99999, }) t.OnFileTokenRequest().Bind(&hook.Handler[*core.FileTokenRequestEvent]{ Func: func(e *core.FileTokenRequestEvent) error { t.registerEventCall("OnFileTokenRequest") return e.Next() }, Priority: -99999, }) t.OnRecordAuthRequest().Bind(&hook.Handler[*core.RecordAuthRequestEvent]{ Func: func(e *core.RecordAuthRequestEvent) error { t.registerEventCall("OnRecordAuthRequest") return e.Next() }, Priority: -99999, }) t.OnRecordAuthWithPasswordRequest().Bind(&hook.Handler[*core.RecordAuthWithPasswordRequestEvent]{ Func: func(e *core.RecordAuthWithPasswordRequestEvent) error { t.registerEventCall("OnRecordAuthWithPasswordRequest") return e.Next() }, Priority: -99999, }) t.OnRecordAuthWithOAuth2Request().Bind(&hook.Handler[*core.RecordAuthWithOAuth2RequestEvent]{ Func: func(e *core.RecordAuthWithOAuth2RequestEvent) error { t.registerEventCall("OnRecordAuthWithOAuth2Request") return e.Next() }, Priority: -99999, }) t.OnRecordAuthRefreshRequest().Bind(&hook.Handler[*core.RecordAuthRefreshRequestEvent]{ Func: func(e *core.RecordAuthRefreshRequestEvent) error { t.registerEventCall("OnRecordAuthRefreshRequest") return e.Next() }, Priority: -99999, }) t.OnRecordRequestPasswordResetRequest().Bind(&hook.Handler[*core.RecordRequestPasswordResetRequestEvent]{ Func: func(e *core.RecordRequestPasswordResetRequestEvent) error { t.registerEventCall("OnRecordRequestPasswordResetRequest") return e.Next() }, Priority: -99999, }) t.OnRecordConfirmPasswordResetRequest().Bind(&hook.Handler[*core.RecordConfirmPasswordResetRequestEvent]{ Func: func(e *core.RecordConfirmPasswordResetRequestEvent) error { t.registerEventCall("OnRecordConfirmPasswordResetRequest") return e.Next() }, Priority: -99999, }) t.OnRecordRequestVerificationRequest().Bind(&hook.Handler[*core.RecordRequestVerificationRequestEvent]{ Func: func(e *core.RecordRequestVerificationRequestEvent) error { t.registerEventCall("OnRecordRequestVerificationRequest") return e.Next() }, Priority: -99999, }) t.OnRecordConfirmVerificationRequest().Bind(&hook.Handler[*core.RecordConfirmVerificationRequestEvent]{ Func: func(e *core.RecordConfirmVerificationRequestEvent) error { t.registerEventCall("OnRecordConfirmVerificationRequest") return e.Next() }, Priority: -99999, }) t.OnRecordRequestEmailChangeRequest().Bind(&hook.Handler[*core.RecordRequestEmailChangeRequestEvent]{ Func: func(e *core.RecordRequestEmailChangeRequestEvent) error { t.registerEventCall("OnRecordRequestEmailChangeRequest") return e.Next() }, Priority: -99999, }) t.OnRecordConfirmEmailChangeRequest().Bind(&hook.Handler[*core.RecordConfirmEmailChangeRequestEvent]{ Func: func(e *core.RecordConfirmEmailChangeRequestEvent) error { t.registerEventCall("OnRecordConfirmEmailChangeRequest") return e.Next() }, Priority: -99999, }) t.OnRecordRequestOTPRequest().Bind(&hook.Handler[*core.RecordCreateOTPRequestEvent]{ Func: func(e *core.RecordCreateOTPRequestEvent) error { t.registerEventCall("OnRecordRequestOTPRequest") return e.Next() }, Priority: -99999, }) t.OnRecordAuthWithOTPRequest().Bind(&hook.Handler[*core.RecordAuthWithOTPRequestEvent]{ Func: func(e *core.RecordAuthWithOTPRequestEvent) error { t.registerEventCall("OnRecordAuthWithOTPRequest") return e.Next() }, Priority: -99999, }) t.OnRecordsListRequest().Bind(&hook.Handler[*core.RecordsListRequestEvent]{ Func: func(e *core.RecordsListRequestEvent) error { t.registerEventCall("OnRecordsListRequest") return e.Next() }, Priority: -99999, }) t.OnRecordViewRequest().Bind(&hook.Handler[*core.RecordRequestEvent]{ Func: func(e *core.RecordRequestEvent) error { t.registerEventCall("OnRecordViewRequest") return e.Next() }, Priority: -99999, }) t.OnRecordCreateRequest().Bind(&hook.Handler[*core.RecordRequestEvent]{ Func: func(e *core.RecordRequestEvent) error { t.registerEventCall("OnRecordCreateRequest") return e.Next() }, Priority: -99999, }) t.OnRecordUpdateRequest().Bind(&hook.Handler[*core.RecordRequestEvent]{ Func: func(e *core.RecordRequestEvent) error { t.registerEventCall("OnRecordUpdateRequest") return e.Next() }, Priority: -99999, }) t.OnRecordDeleteRequest().Bind(&hook.Handler[*core.RecordRequestEvent]{ Func: func(e *core.RecordRequestEvent) error { t.registerEventCall("OnRecordDeleteRequest") return e.Next() }, Priority: -99999, }) t.OnCollectionsListRequest().Bind(&hook.Handler[*core.CollectionsListRequestEvent]{ Func: func(e *core.CollectionsListRequestEvent) error { t.registerEventCall("OnCollectionsListRequest") return e.Next() }, Priority: -99999, }) t.OnCollectionViewRequest().Bind(&hook.Handler[*core.CollectionRequestEvent]{ Func: func(e *core.CollectionRequestEvent) error { t.registerEventCall("OnCollectionViewRequest") return e.Next() }, Priority: -99999, }) t.OnCollectionCreateRequest().Bind(&hook.Handler[*core.CollectionRequestEvent]{ Func: func(e *core.CollectionRequestEvent) error { t.registerEventCall("OnCollectionCreateRequest") return e.Next() }, Priority: -99999, }) t.OnCollectionUpdateRequest().Bind(&hook.Handler[*core.CollectionRequestEvent]{ Func: func(e *core.CollectionRequestEvent) error { t.registerEventCall("OnCollectionUpdateRequest") return e.Next() }, Priority: -99999, }) t.OnCollectionDeleteRequest().Bind(&hook.Handler[*core.CollectionRequestEvent]{ Func: func(e *core.CollectionRequestEvent) error { t.registerEventCall("OnCollectionDeleteRequest") return e.Next() }, Priority: -99999, }) t.OnCollectionsImportRequest().Bind(&hook.Handler[*core.CollectionsImportRequestEvent]{ Func: func(e *core.CollectionsImportRequestEvent) error { t.registerEventCall("OnCollectionsImportRequest") return e.Next() }, Priority: -99999, }) t.OnBatchRequest().Bind(&hook.Handler[*core.BatchRequestEvent]{ Func: func(e *core.BatchRequestEvent) error { t.registerEventCall("OnBatchRequest") return e.Next() }, Priority: -99999, }) return t, nil } // TempDirClone creates a new temporary directory copy from the // provided directory path. // // It is the caller's responsibility to call `os.RemoveAll(tempDir)` // when the directory is no longer needed! func TempDirClone(dirToClone string) (string, error) { tempDir, err := os.MkdirTemp("", "pb_test_*") if err != nil { return "", err } // copy everything from testDataDir to tempDir if err := copyDir(dirToClone, tempDir); err != nil { return "", err } return tempDir, nil } // ------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------- func copyDir(src string, dest string) error { if err := os.MkdirAll(dest, os.ModePerm); err != nil { return err } sourceDir, err := os.Open(src) if err != nil { return err } defer sourceDir.Close() items, err := sourceDir.Readdir(-1) if err != nil { return err } for _, item := range items { fullSrcPath := filepath.Join(src, item.Name()) fullDestPath := filepath.Join(dest, item.Name()) var copyErr error if item.IsDir() { copyErr = copyDir(fullSrcPath, fullDestPath) } else { copyErr = copyFile(fullSrcPath, fullDestPath) } if copyErr != nil { return copyErr } } return nil } func copyFile(src string, dest string) error { srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() destFile, err := os.Create(dest) if err != nil { return err } defer destFile.Close() if _, err := io.Copy(destFile, srcFile); err != nil { return err } return nil }