Files
Erupe/server/api/repo_mocks_test.go
Houmgaor 5fe1b22550 feat(save-transfer): add saveutil CLI and token-gated import endpoint
Adds two complementary paths for transferring character save data between
Erupe instances without breaking the SHA-256 integrity check system:

- `cmd/saveutil/`: admin CLI with `import`, `export`, `grant-import`, and
  `revoke-import` subcommands. Direct DB access; no server running required.
- `POST /v2/characters/{id}/import`: player-facing API endpoint gated behind
  a one-time token issued by `saveutil grant-import` (default TTL 24 h).
  Token is validated and consumed atomically to prevent TOCTOU races.
- Migration `0013_save_transfer`: `savedata_import_token` and
  `savedata_import_token_expiry` columns on `characters` table.
- Both paths decompress incoming savedata and recompute the SHA-256 hash
  server-side, so the integrity check remains valid after import.
- README documents both methods and the per-character hash-reset workaround.

Closes #183.
2026-03-21 20:14:58 +01:00

158 lines
4.0 KiB
Go

package api
import (
"context"
"time"
)
// mockAPIUserRepo implements APIUserRepo for testing.
type mockAPIUserRepo struct {
registerID uint32
registerRights uint32
registerErr error
credentialsID uint32
credentialsPassword string
credentialsRights uint32
credentialsErr error
lastLogin time.Time
lastLoginErr error
returnExpiry time.Time
returnExpiryErr error
updateReturnExpiryErr error
updateLastLoginErr error
}
func (m *mockAPIUserRepo) Register(_ context.Context, _, _ string, _ time.Time) (uint32, uint32, error) {
return m.registerID, m.registerRights, m.registerErr
}
func (m *mockAPIUserRepo) GetCredentials(_ context.Context, _ string) (uint32, string, uint32, error) {
return m.credentialsID, m.credentialsPassword, m.credentialsRights, m.credentialsErr
}
func (m *mockAPIUserRepo) GetLastLogin(_ uint32) (time.Time, error) {
return m.lastLogin, m.lastLoginErr
}
func (m *mockAPIUserRepo) GetReturnExpiry(_ uint32) (time.Time, error) {
return m.returnExpiry, m.returnExpiryErr
}
func (m *mockAPIUserRepo) UpdateReturnExpiry(_ uint32, _ time.Time) error {
return m.updateReturnExpiryErr
}
func (m *mockAPIUserRepo) UpdateLastLogin(_ uint32, _ time.Time) error {
return m.updateLastLoginErr
}
// mockAPICharacterRepo implements APICharacterRepo for testing.
type mockAPICharacterRepo struct {
newCharacter Character
newCharacterErr error
countForUser int
countForUserErr error
createChar Character
createCharErr error
isNewResult bool
isNewErr error
hardDeleteErr error
softDeleteErr error
characters []Character
charactersErr error
exportResult map[string]interface{}
exportErr error
grantImportTokenErr error
revokeImportTokenErr error
importSaveErr error
}
func (m *mockAPICharacterRepo) GetNewCharacter(_ context.Context, _ uint32) (Character, error) {
return m.newCharacter, m.newCharacterErr
}
func (m *mockAPICharacterRepo) CountForUser(_ context.Context, _ uint32) (int, error) {
return m.countForUser, m.countForUserErr
}
func (m *mockAPICharacterRepo) Create(_ context.Context, _ uint32, _ uint32) (Character, error) {
return m.createChar, m.createCharErr
}
func (m *mockAPICharacterRepo) IsNew(_ uint32) (bool, error) {
return m.isNewResult, m.isNewErr
}
func (m *mockAPICharacterRepo) HardDelete(_ uint32) error {
return m.hardDeleteErr
}
func (m *mockAPICharacterRepo) SoftDelete(_ uint32) error {
return m.softDeleteErr
}
func (m *mockAPICharacterRepo) GetForUser(_ context.Context, _ uint32) ([]Character, error) {
return m.characters, m.charactersErr
}
func (m *mockAPICharacterRepo) ExportSave(_ context.Context, _, _ uint32) (map[string]interface{}, error) {
return m.exportResult, m.exportErr
}
func (m *mockAPICharacterRepo) GrantImportToken(_ context.Context, _, _ uint32, _ string, _ time.Time) error {
return m.grantImportTokenErr
}
func (m *mockAPICharacterRepo) RevokeImportToken(_ context.Context, _, _ uint32) error {
return m.revokeImportTokenErr
}
func (m *mockAPICharacterRepo) ImportSave(_ context.Context, _, _ uint32, _ string, _ SaveBlobs) error {
return m.importSaveErr
}
// mockAPIEventRepo implements APIEventRepo for testing.
type mockAPIEventRepo struct {
featureWeapon *FeatureWeaponRow
featureWeaponErr error
events []EventRow
eventsErr error
}
func (m *mockAPIEventRepo) GetFeatureWeapon(_ context.Context, _ time.Time) (*FeatureWeaponRow, error) {
return m.featureWeapon, m.featureWeaponErr
}
func (m *mockAPIEventRepo) GetActiveEvents(_ context.Context, _ string) ([]EventRow, error) {
return m.events, m.eventsErr
}
// mockAPISessionRepo implements APISessionRepo for testing.
type mockAPISessionRepo struct {
createTokenID uint32
createTokenErr error
userID uint32
userIDErr error
}
func (m *mockAPISessionRepo) CreateToken(_ context.Context, _ uint32, _ string) (uint32, error) {
return m.createTokenID, m.createTokenErr
}
func (m *mockAPISessionRepo) GetUserIDByToken(_ context.Context, _ string) (uint32, error) {
return m.userID, m.userIDErr
}