Compare commits
5 Commits
v2025.0720
...
main
Author | SHA1 | Date | |
---|---|---|---|
501fa65d76 | |||
507897d9a1 | |||
9c98ffcb86 | |||
938f4ac323 | |||
c507b1405e |
@ -45,7 +45,9 @@ Based on analysis of the current codebase, the multi-server support feature need
|
||||
|
||||
## Migration and Compatibility Tasks
|
||||
|
||||
- [-] 4. Implement migration system for existing installations
|
||||
- [x] 4. Implement migration system for existing installations
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -56,7 +58,13 @@ Based on analysis of the current codebase, the multi-server support feature need
|
||||
- Add migration error handling and rollback capabilities
|
||||
- _Requirements: 4.4, 4.5, 6.1, 6.2, 6.3, 6.5_
|
||||
|
||||
- [ ] 5. Ensure backward compatibility
|
||||
- [x] 5. Ensure backward compatibility
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- Implement default server configuration (getpkg.xyz) when no config exists
|
||||
- Maintain existing CLI behavior for users without custom server configuration
|
||||
- Preserve existing token storage location compatibility
|
||||
@ -65,14 +73,24 @@ Based on analysis of the current codebase, the multi-server support feature need
|
||||
|
||||
## CLI Integration Tasks
|
||||
|
||||
- [ ] 6. Add server management commands to main.cpp
|
||||
- [x] 6. Add server management commands to main.cpp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- Implement `getpkg server add <url>` command
|
||||
- Implement `getpkg server remove <url>` command
|
||||
- Implement `getpkg server list` command
|
||||
- Add server URL validation and user feedback
|
||||
- _Requirements: 1.1, 1.2, 1.3_
|
||||
|
||||
- [ ] 7. Update existing commands for multi-server support
|
||||
- [x] 7. Update existing commands for multi-server support
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- Modify install command to use ServerManager and multi-server GetbinClient
|
||||
- Update publish command to support --server option and default server selection
|
||||
- Update unpublish command to support --server option and default server selection
|
||||
@ -81,14 +99,22 @@ Based on analysis of the current codebase, the multi-server support feature need
|
||||
|
||||
## Integration and Testing Tasks
|
||||
|
||||
- [ ] 8. Integrate all components in main application flow
|
||||
- [x] 8. Integrate all components in main application flow
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- Initialize ServerManager in main.cpp startup
|
||||
- Trigger migration process on first run with new version
|
||||
- Update package installation flow to use enhanced metadata
|
||||
- Ensure proper error handling and user messaging throughout
|
||||
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
|
||||
|
||||
- [ ] 9. Add comprehensive error handling and validation
|
||||
- [-] 9. Add comprehensive error handling and validation
|
||||
|
||||
|
||||
|
||||
- Implement network error handling with server fallback
|
||||
- Add configuration file corruption recovery
|
||||
- Create user-friendly error messages for server connectivity issues
|
||||
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -90,5 +90,9 @@
|
||||
"__tree": "cpp",
|
||||
"queue": "cpp",
|
||||
"stack": "cpp"
|
||||
}
|
||||
},
|
||||
"kiroAgent.enableTabAutocomplete": true,
|
||||
"kiroAgent.trustedCommands": [
|
||||
"bash *"
|
||||
]
|
||||
}
|
168
ARCHITECTURE.md
Normal file
168
ARCHITECTURE.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Architecture Overview
|
||||
|
||||
This document provides a detailed technical overview of the getpkg project and its associated tools.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The repository contains multiple tools in the dropshell ecosystem:
|
||||
|
||||
- **getpkg** - The main C++ package manager
|
||||
- **sos** - Simple object storage upload utility
|
||||
- **whatsdirty** - Git repository status checker for subdirectories
|
||||
- **dehydrate** - File to C++ code generator
|
||||
- **bb64** - Base64 encoder/decoder
|
||||
- **gp** - Git push utility
|
||||
|
||||
## getpkg Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
The main getpkg application is built in C++ using modern C++23 standards with the following key components:
|
||||
|
||||
#### Package Management
|
||||
- **`GetbinClient`** (`src/GetbinClient.{hpp,cpp}`)
|
||||
- HTTP client with multi-server support for downloading/uploading packages
|
||||
- Implements fallback logic for server failures
|
||||
- Progress callback support for downloads/uploads
|
||||
- Server-specific and multi-server operations
|
||||
|
||||
- **`PackageMetadata`** (`src/PackageMetadata.{hpp,cpp}`)
|
||||
- Enhanced metadata structure with server source tracking
|
||||
- Supports migration from legacy single-server format
|
||||
- Stores: name, version, hash, architecture, source server, install date
|
||||
- JSON serialization/deserialization
|
||||
- Validation methods for all fields
|
||||
|
||||
- **`PackageMetadataManager`** (`src/PackageMetadata.{hpp,cpp}`)
|
||||
- Manages package metadata directory structure
|
||||
- Handles legacy format migration
|
||||
- Package enumeration and validation
|
||||
|
||||
#### Server Management
|
||||
- **`ServerManager`** (`src/ServerManager.{hpp,cpp}`)
|
||||
- Manages multiple package servers with write tokens
|
||||
- Server configuration persistence
|
||||
- Token management for publishing
|
||||
- Server reachability validation
|
||||
|
||||
#### System Integration
|
||||
- **`BashrcEditor`** (`src/BashrcEditor.{hpp,cpp}`)
|
||||
- Manages `~/.bashrc_getpkg` file modifications
|
||||
- Handles PATH updates and bash completions
|
||||
- Safe file editing with atomic operations
|
||||
|
||||
- **`DropshellScriptManager`** (`src/DropshellScriptManager.{hpp,cpp}`)
|
||||
- Manages tool installation and configuration
|
||||
- Handles setup scripts execution
|
||||
- Directory structure management
|
||||
|
||||
- **`MigrationManager`** (`src/MigrationManager.{hpp,cpp}`)
|
||||
- Handles migrations between getpkg versions
|
||||
- Legacy format conversions
|
||||
- Configuration updates
|
||||
|
||||
### Common Utilities
|
||||
Located in `src/common/`:
|
||||
- **`archive_tgz`** - TAR.GZ archive creation/extraction
|
||||
- **`hash`** - File hashing utilities
|
||||
- **`output`** - Formatted console output
|
||||
- **`temp_directory`** - Temporary directory management
|
||||
- **`xxhash`** - Fast hashing algorithm implementation
|
||||
|
||||
## Build System
|
||||
|
||||
### Docker-Based Build
|
||||
- Uses containerized build environment: `gitea.jde.nz/public/dropshell-build-base:latest`
|
||||
- Ensures consistent builds across different host systems
|
||||
- Static linking for maximum portability
|
||||
|
||||
### CMake Configuration
|
||||
- C++23 standard required
|
||||
- Static executable building (`-static` linker flags)
|
||||
- External dependencies:
|
||||
- nlohmann_json - JSON parsing
|
||||
- CPRStatic - HTTP client library
|
||||
- Version format: `YYYY.MMDD.HHMM` (timestamp-based)
|
||||
|
||||
### Build Scripts
|
||||
- **`build.sh`** - Individual tool build script
|
||||
- **`test.sh`** - Run tests for individual tools
|
||||
- **`publish.sh`** - Publish tool to getpkg.xyz (requires SOS_WRITE_TOKEN)
|
||||
- **`buildtestpublish_all.sh`** - Master script that builds, tests, and publishes all tools
|
||||
|
||||
## File Locations and Structure
|
||||
|
||||
### User Installation
|
||||
- **Tool installations**: `~/.getpkg/<tool_name>/`
|
||||
- **Executable symlinks**: `~/.local/bin/getpkg/`
|
||||
- **Configuration**: `~/.config/getpkg/`
|
||||
- `packages/<tool_name>.json` - Package metadata
|
||||
- `servers.json` - Server configuration
|
||||
- **Bash integration**: `~/.bashrc_getpkg` (sourced by ~/.bashrc)
|
||||
|
||||
### Repository Structure
|
||||
```
|
||||
getpkg/
|
||||
├── getpkg/ # Main package manager
|
||||
│ ├── src/ # Source code
|
||||
│ ├── test/ # Test suite
|
||||
│ └── build.sh # Build script
|
||||
├── sos/ # Simple object storage
|
||||
├── whatsdirty/ # Git status checker
|
||||
├── dehydrate/ # File to C++ converter
|
||||
├── bb64/ # Base64 utility
|
||||
├── gp/ # Git push utility
|
||||
└── buildtestpublish_all.sh # Master build script
|
||||
```
|
||||
|
||||
## Multi-Server Architecture
|
||||
|
||||
getpkg supports multiple package servers with intelligent fallback:
|
||||
|
||||
1. **Server Configuration**
|
||||
- Multiple servers can be configured
|
||||
- Each server can have an optional write token
|
||||
- First server with token becomes default publish target
|
||||
|
||||
2. **Download Strategy**
|
||||
- Attempts servers in configured order
|
||||
- Falls back to next server on failure
|
||||
- Tracks which server provided each package
|
||||
|
||||
3. **Publishing**
|
||||
- Requires SOS_WRITE_TOKEN environment variable
|
||||
- Publishes to first server with valid token
|
||||
- Supports architecture-specific uploads
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- No root access required - installs to user home directory
|
||||
- Static linking prevents dependency attacks
|
||||
- Hash verification for downloaded packages
|
||||
- Token-based authentication for publishing
|
||||
|
||||
## Testing
|
||||
|
||||
- Docker-based test environment for consistency
|
||||
- Integration tests for package operations
|
||||
- Unit tests for individual components
|
||||
- Test artifacts isolated in `test_temp/` directory
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Local Development**
|
||||
```bash
|
||||
cd getpkg && ./build.sh # Build
|
||||
cd getpkg && ./test.sh # Test
|
||||
```
|
||||
|
||||
2. **Full Build**
|
||||
```bash
|
||||
./buildtestpublish_all.sh # Build all tools
|
||||
```
|
||||
|
||||
3. **Publishing** (requires SOS_WRITE_TOKEN)
|
||||
```bash
|
||||
export SOS_WRITE_TOKEN="your-token"
|
||||
cd getpkg && ./publish.sh # Publish single tool
|
||||
```
|
60
CLAUDE.md
60
CLAUDE.md
@ -6,19 +6,35 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
This repository contains Dropshell Tools - a collection of utilities that support dropshell development. The main tool is `getpkg`, a C++ command-line application that manages tool installation, updates, and publishing for the dropshell ecosystem.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
- **getpkg**: Package manager for dropshell tools (C++23)
|
||||
- **sos**: Simple object storage upload utility (Bash)
|
||||
- **whatsdirty**: Git repository status checker (Bash)
|
||||
- **dehydrate**: File to C++ code generator (C++)
|
||||
- **bb64**: Base64 encoder/decoder (C++)
|
||||
- **gp**: Git push utility (Bash)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
### Core Components (getpkg)
|
||||
|
||||
- **getpkg**: Main C++ application (`getpkg/src/`)
|
||||
- `main.cpp`: CLI interface and command routing
|
||||
- `ArchiveManager`: Handles .tgz archive creation/extraction
|
||||
- `BashrcEditor`: Manages ~/.bashrc_dropshell_tool script modifications
|
||||
- `GetbinClient`: Multi-server HTTP client for downloading/uploading tools
|
||||
- `PackageMetadata`: Enhanced metadata with server tracking and migration support
|
||||
- `ServerManager`: Manages multiple package servers with write tokens
|
||||
- `BashrcEditor`: Manages ~/.bashrc_getpkg script modifications
|
||||
- `DropshellScriptManager`: Manages tool installation and configuration
|
||||
- `GetbinClient`: HTTP client for downloading/uploading tools
|
||||
- `MigrationManager`: Handles legacy format migrations
|
||||
- `common/`: Shared utilities (archive_tgz, hash, output, temp_directory)
|
||||
|
||||
- **sos**: Simple object storage utility
|
||||
- **whatsdirty**: Git repository status checker
|
||||
### Key Features
|
||||
|
||||
- **Multi-server support**: Fallback logic for package downloads
|
||||
- **Architecture awareness**: Supports x86_64, aarch64, and universal packages
|
||||
- **Migration support**: Handles upgrades from legacy single-server format
|
||||
- **Static linking**: All tools built as static binaries for portability
|
||||
|
||||
### Build System
|
||||
|
||||
@ -59,11 +75,33 @@ export CMAKE_BUILD_TYPE="Release"
|
||||
## Tool Functionality
|
||||
|
||||
getpkg manages a tool ecosystem by:
|
||||
- Installing tools to `~/.local/bin/getpkg/<tool_name>/`
|
||||
- Managing bash completions and aliases via `~/.bashrc_getpkg`
|
||||
- Storing tool metadata in `~/.config/getpkg/`
|
||||
- Publishing/downloading tools via getbin.xyz object storage
|
||||
- Installing tools to `~/.getpkg/<tool_name>/` with symlinks in `~/.local/bin/getpkg/`
|
||||
- Managing bash completions and PATH updates via `~/.bashrc_getpkg`
|
||||
- Storing tool metadata in `~/.config/getpkg/packages/` (JSON format)
|
||||
- Supporting multi-server package distribution with fallback
|
||||
- Publishing/downloading tools via object storage servers (default: getpkg.xyz)
|
||||
|
||||
## File Locations
|
||||
|
||||
- **Tool installations**: `~/.getpkg/<tool_name>/`
|
||||
- **Executable symlinks**: `~/.local/bin/getpkg/` (added to PATH)
|
||||
- **Package metadata**: `~/.config/getpkg/packages/<tool_name>.json`
|
||||
- **Server configuration**: `~/.config/getpkg/servers.json`
|
||||
- **Bash integration**: `~/.bashrc_getpkg` (sourced by ~/.bashrc)
|
||||
|
||||
## Publishing Requirements
|
||||
|
||||
Publishing requires the `SOS_WRITE_TOKEN` environment variable for authentication to the object storage system.
|
||||
Publishing requires the `SOS_WRITE_TOKEN` environment variable for authentication to the object storage system.
|
||||
|
||||
## Testing
|
||||
|
||||
- Tests create temporary files in `test_temp/` directory
|
||||
- Docker-based test environment using same build image
|
||||
- Run `cleanup_test_packages.sh` to remove orphaned test packages from servers
|
||||
|
||||
## Important Notes
|
||||
|
||||
- All builds use static linking for maximum portability
|
||||
- Version format is YYYY.MMDD.HHMM (timestamp-based)
|
||||
- Tools should support `version` and `autocomplete` subcommands
|
||||
- Architecture-specific builds use suffixes like `:x86_64` or `:aarch64`
|
@ -58,6 +58,8 @@
|
||||
#include "DropshellScriptManager.hpp"
|
||||
#include "GetbinClient.hpp"
|
||||
#include "MigrationManager.hpp"
|
||||
#include "ServerManager.hpp"
|
||||
#include "PackageMetadata.hpp"
|
||||
#include "archive_tgz.hpp"
|
||||
#include "hash.hpp"
|
||||
#include <iostream>
|
||||
@ -164,25 +166,47 @@ int install_tool(int argc, char* argv[]) {
|
||||
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
|
||||
std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName;
|
||||
std::filesystem::path archivePath = tempDir.path() / (toolName + ".tgz");
|
||||
std::filesystem::path toolInfoPath = configDir / (toolName + ".json");
|
||||
|
||||
// Initialize ServerManager and get server list
|
||||
ServerManager serverManager;
|
||||
if (!serverManager.loadConfiguration()) {
|
||||
std::cerr << "Failed to load server configuration" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
if (servers.empty()) {
|
||||
std::cerr << "No servers configured" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize PackageMetadataManager
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
if (!packageManager.ensurePackagesDirectory()) {
|
||||
std::cerr << "Failed to create packages directory" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check if tool needs update or install
|
||||
if (std::filesystem::exists(toolInfoPath)) {
|
||||
bool isUpdate = false;
|
||||
PackageMetadata existingMetadata;
|
||||
if (packageManager.packageExists(toolName)) {
|
||||
// Tool exists, check if update needed
|
||||
std::ifstream tfile(toolInfoPath);
|
||||
json toolInfo;
|
||||
tfile >> toolInfo;
|
||||
tfile.close();
|
||||
existingMetadata = packageManager.loadPackageMetadata(toolName);
|
||||
if (!existingMetadata.isValid()) {
|
||||
std::cerr << "Warning: Invalid existing package metadata for " << toolName << std::endl;
|
||||
}
|
||||
|
||||
std::string localHash = toolInfo.value("hash", "");
|
||||
std::string localArch = toolInfo.value("arch", arch);
|
||||
std::string localHash = existingMetadata.hash;
|
||||
std::string localArch = existingMetadata.arch.empty() ? arch : existingMetadata.arch;
|
||||
|
||||
// Get remote hash to compare - use the same arch that was originally installed
|
||||
GetbinClient getbin;
|
||||
// Get remote hash to compare - use multi-server GetbinClient
|
||||
GetbinClient getbin(servers);
|
||||
std::string remoteHash;
|
||||
if (getbin.getHash(toolName, localArch, remoteHash) && !remoteHash.empty()) {
|
||||
if (localHash != remoteHash) {
|
||||
std::cout << "Updating " << toolName << "..." << std::endl;
|
||||
isUpdate = true;
|
||||
} else {
|
||||
std::cout << toolName << " is already up to date." << std::endl;
|
||||
return 0;
|
||||
@ -190,6 +214,7 @@ int install_tool(int argc, char* argv[]) {
|
||||
} else {
|
||||
// If we can't get remote hash, assume update is needed
|
||||
std::cout << "Updating " << toolName << "..." << std::endl;
|
||||
isUpdate = true;
|
||||
}
|
||||
} else {
|
||||
std::cout << "Installing " << toolName << "..." << std::endl;
|
||||
@ -209,9 +234,10 @@ int install_tool(int argc, char* argv[]) {
|
||||
if (std::filesystem::exists(binDir))
|
||||
std::filesystem::remove_all(binDir);
|
||||
|
||||
// Download tool - try arch-specific version first, then universal fallback
|
||||
GetbinClient getbin2;
|
||||
// Download tool using multi-server GetbinClient - try arch-specific version first, then universal fallback
|
||||
GetbinClient getbin2(servers);
|
||||
std::string downloadArch = arch;
|
||||
std::string sourceServer;
|
||||
|
||||
// Progress callback for downloads
|
||||
auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool {
|
||||
@ -236,6 +262,12 @@ int install_tool(int argc, char* argv[]) {
|
||||
}
|
||||
clearAndPrint("Downloading " + toolName + "... done\n");
|
||||
|
||||
// Find which server provided the package
|
||||
if (!getbin2.findPackageServer(toolName, downloadArch, sourceServer)) {
|
||||
// Fallback to first server if we can't determine the source
|
||||
sourceServer = servers[0];
|
||||
}
|
||||
|
||||
// Unpack tool
|
||||
std::cout << "Unpacking..." << std::flush;
|
||||
if (!common::unpack_tgz(archivePath.string(), binDir.string())) {
|
||||
@ -271,16 +303,11 @@ int install_tool(int argc, char* argv[]) {
|
||||
std::cerr << "Warning: Failed to get version for " << toolName << std::endl;
|
||||
}
|
||||
|
||||
// Save tool info
|
||||
json toolInfo = {
|
||||
{"name", toolName},
|
||||
{"version", version},
|
||||
{"hash", hash},
|
||||
{"arch", downloadArch}
|
||||
};
|
||||
std::ofstream toolInfoFile(toolInfoPath);
|
||||
toolInfoFile << toolInfo.dump(2);
|
||||
toolInfoFile.close();
|
||||
// Create and save enhanced package metadata
|
||||
PackageMetadata metadata(toolName, version, hash, downloadArch, sourceServer);
|
||||
if (!packageManager.savePackageMetadata(metadata)) {
|
||||
std::cerr << "Warning: Failed to save package metadata for " << toolName << std::endl;
|
||||
}
|
||||
|
||||
// Run setup script if exists
|
||||
std::filesystem::path setupScriptPath = binDir / "setup_script.sh";
|
||||
@ -296,11 +323,27 @@ int install_tool(int argc, char* argv[]) {
|
||||
|
||||
int publish_tool(int argc, char* argv[]) {
|
||||
if (argc < 4) {
|
||||
std::cerr << "Usage: getpkg publish <tool_name:ARCH> <folder>" << std::endl;
|
||||
std::cerr << "Usage: getpkg publish [--server <url>] <tool_name:ARCH> <folder>" << std::endl;
|
||||
std::cerr << " getpkg publish <tool_name:ARCH> <folder>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string labeltag = argv[2];
|
||||
std::string folder = argv[3];
|
||||
|
||||
// Parse arguments for --server option
|
||||
std::string targetServer;
|
||||
std::string labeltag;
|
||||
std::string folder;
|
||||
int argIndex = 2;
|
||||
|
||||
if (argc >= 5 && std::string(argv[2]) == "--server") {
|
||||
targetServer = argv[3];
|
||||
labeltag = argv[4];
|
||||
folder = argv[5];
|
||||
argIndex = 5;
|
||||
} else {
|
||||
labeltag = argv[2];
|
||||
folder = argv[3];
|
||||
argIndex = 3;
|
||||
}
|
||||
|
||||
// If no ARCH is provided (no colon in labeltag), append ":universal" for cross-platform tools
|
||||
if (labeltag.find(':') == std::string::npos) {
|
||||
@ -315,6 +358,49 @@ int publish_tool(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize ServerManager
|
||||
ServerManager serverManager;
|
||||
if (!serverManager.loadConfiguration()) {
|
||||
std::cerr << "Failed to load server configuration" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Determine target server
|
||||
std::string publishServer;
|
||||
if (!targetServer.empty()) {
|
||||
// User specified a server, validate it exists in configuration
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
if (std::find(servers.begin(), servers.end(), targetServer) == servers.end()) {
|
||||
std::cerr << "Error: Server '" << targetServer << "' is not configured" << std::endl;
|
||||
std::cerr << "Use 'getpkg server add " << targetServer << "' to add it first" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
publishServer = targetServer;
|
||||
} else {
|
||||
// Use default publish server (first server with write token)
|
||||
publishServer = serverManager.getDefaultPublishServer();
|
||||
if (publishServer.empty()) {
|
||||
std::cerr << "Error: No servers with write tokens configured" << std::endl;
|
||||
std::cerr << "Use 'getpkg server add <url>' and provide a write token" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get write token for the target server
|
||||
std::string token = serverManager.getWriteToken(publishServer);
|
||||
if (token.empty()) {
|
||||
// Check environment variable as fallback
|
||||
const char* envToken = std::getenv("SOS_WRITE_TOKEN");
|
||||
if (envToken && std::strlen(envToken) > 0) {
|
||||
token = envToken;
|
||||
} else {
|
||||
std::cerr << "Error: No write token found for server '" << publishServer << "'" << std::endl;
|
||||
std::cerr << "Set SOS_WRITE_TOKEN environment variable or configure token for this server" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string home = get_home();
|
||||
std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz");
|
||||
std::filesystem::create_directories(archivePath.parent_path());
|
||||
@ -323,24 +409,10 @@ int publish_tool(int argc, char* argv[]) {
|
||||
std::cerr << "Failed to create archive." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string token;
|
||||
const char* envToken = std::getenv("SOS_WRITE_TOKEN");
|
||||
if (envToken && std::strlen(envToken) > 0) {
|
||||
token = envToken;
|
||||
} else {
|
||||
std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt";
|
||||
if (std::filesystem::exists(tokenPath)) {
|
||||
std::ifstream tfile(tokenPath);
|
||||
std::getline(tfile, token);
|
||||
} else {
|
||||
std::cout << "Enter getpkg.xyz write token: ";
|
||||
std::getline(std::cin, token);
|
||||
std::filesystem::create_directories(tokenPath.parent_path());
|
||||
std::ofstream tfile(tokenPath);
|
||||
tfile << token << std::endl;
|
||||
}
|
||||
}
|
||||
GetbinClient getbin;
|
||||
|
||||
// Initialize GetbinClient with server list
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
GetbinClient getbin(servers);
|
||||
std::string url, hash;
|
||||
|
||||
// Progress callback for upload
|
||||
@ -354,13 +426,14 @@ int publish_tool(int argc, char* argv[]) {
|
||||
return true; // Continue upload
|
||||
};
|
||||
|
||||
std::cout << "Publishing to " << publishServer << "..." << std::endl;
|
||||
std::cout << "Uploading..." << std::flush;
|
||||
if (!getbin.upload(archivePath.string(), url, hash, token, uploadProgressCallback)) {
|
||||
std::cerr << "\rFailed to upload archive." << std::endl;
|
||||
if (!getbin.upload(publishServer, archivePath.string(), url, hash, token, uploadProgressCallback)) {
|
||||
std::cerr << "\rFailed to upload archive to " << publishServer << std::endl;
|
||||
return 1;
|
||||
}
|
||||
clearAndPrint("Uploading... done\n");
|
||||
std::cout << "Published! URL: " << url << "\nHash: " << hash << std::endl;
|
||||
std::cout << "Published to " << publishServer << "! URL: " << url << "\nHash: " << hash << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -368,6 +441,25 @@ int update_tool(int argc, char* argv[]) {
|
||||
std::string home = get_home();
|
||||
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
|
||||
|
||||
// Initialize ServerManager and PackageMetadataManager
|
||||
ServerManager serverManager;
|
||||
if (!serverManager.loadConfiguration()) {
|
||||
std::cerr << "Failed to load server configuration" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
if (servers.empty()) {
|
||||
std::cerr << "No servers configured" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
if (!packageManager.ensurePackagesDirectory()) {
|
||||
std::cerr << "Failed to create packages directory" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Structure to hold tool information
|
||||
struct ToolInfo {
|
||||
std::string name;
|
||||
@ -375,29 +467,43 @@ int update_tool(int argc, char* argv[]) {
|
||||
std::string remoteHash;
|
||||
std::string arch;
|
||||
std::string version;
|
||||
std::string sourceServer;
|
||||
bool needsUpdate = false;
|
||||
std::string status = "Up to date";
|
||||
};
|
||||
|
||||
std::vector<ToolInfo> tools;
|
||||
|
||||
// Collect all installed tools
|
||||
if (std::filesystem::exists(configDir)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
std::string tname = entry.path().stem();
|
||||
|
||||
ToolInfo tool;
|
||||
tool.name = tname;
|
||||
|
||||
// Read local tool info
|
||||
std::ifstream tfile(entry.path());
|
||||
// Collect all installed tools using PackageMetadataManager
|
||||
std::vector<std::string> installedPackages = packageManager.listInstalledPackages();
|
||||
for (const std::string& toolName : installedPackages) {
|
||||
ToolInfo tool;
|
||||
tool.name = toolName;
|
||||
|
||||
// Load package metadata
|
||||
PackageMetadata metadata = packageManager.loadPackageMetadata(toolName);
|
||||
if (metadata.isValid()) {
|
||||
tool.localHash = metadata.hash;
|
||||
tool.arch = metadata.arch.empty() ? get_arch() : metadata.arch;
|
||||
tool.version = metadata.version;
|
||||
tool.sourceServer = metadata.sourceServer;
|
||||
|
||||
if (tool.version.empty() || tool.version == "-") {
|
||||
tool.version = "installed";
|
||||
}
|
||||
} else {
|
||||
// Fallback to legacy format if new format fails
|
||||
std::filesystem::path legacyPath = configDir / (toolName + ".json");
|
||||
if (std::filesystem::exists(legacyPath)) {
|
||||
std::ifstream tfile(legacyPath);
|
||||
if (tfile.good()) {
|
||||
json toolInfo;
|
||||
tfile >> toolInfo;
|
||||
tool.localHash = toolInfo.value("hash", "");
|
||||
tool.arch = toolInfo.value("arch", get_arch());
|
||||
tool.version = toolInfo.value("version", "-");
|
||||
tool.sourceServer = "getpkg.xyz"; // Default for legacy
|
||||
|
||||
if (!tool.version.empty() && tool.version.back() == '\n') {
|
||||
tool.version.pop_back();
|
||||
}
|
||||
@ -405,10 +511,10 @@ int update_tool(int argc, char* argv[]) {
|
||||
tool.version = "installed";
|
||||
}
|
||||
}
|
||||
|
||||
tools.push_back(tool);
|
||||
}
|
||||
}
|
||||
|
||||
tools.push_back(tool);
|
||||
}
|
||||
|
||||
if (tools.empty()) {
|
||||
@ -419,14 +525,14 @@ int update_tool(int argc, char* argv[]) {
|
||||
// Step 1: Check for updates (with progress)
|
||||
std::cout << "Checking " << tools.size() << " tools for updates..." << std::endl;
|
||||
|
||||
GetbinClient getbin;
|
||||
GetbinClient getbin(servers);
|
||||
for (size_t i = 0; i < tools.size(); ++i) {
|
||||
auto& tool = tools[i];
|
||||
|
||||
// Show progress
|
||||
std::cout << "\r[" << (i + 1) << "/" << tools.size() << "] Checking " << tool.name << "..." << std::flush;
|
||||
|
||||
// Check remote hash
|
||||
// Check remote hash - use multi-server fallback
|
||||
std::string remoteHash;
|
||||
if (getbin.getHash(tool.name, tool.arch, remoteHash) && !remoteHash.empty()) {
|
||||
tool.remoteHash = remoteHash;
|
||||
@ -498,16 +604,10 @@ int update_tool(int argc, char* argv[]) {
|
||||
tool.status = "Updated";
|
||||
clearAndPrint("Updated\n");
|
||||
|
||||
// Re-read version after update
|
||||
std::filesystem::path toolInfoPath = configDir / (tool.name + ".json");
|
||||
if (std::filesystem::exists(toolInfoPath)) {
|
||||
std::ifstream tfile(toolInfoPath);
|
||||
json toolInfo;
|
||||
tfile >> toolInfo;
|
||||
tool.version = toolInfo.value("version", tool.version);
|
||||
if (!tool.version.empty() && tool.version.back() == '\n') {
|
||||
tool.version.pop_back();
|
||||
}
|
||||
// Re-read version after update using PackageMetadataManager
|
||||
PackageMetadata updatedMetadata = packageManager.loadPackageMetadata(tool.name);
|
||||
if (updatedMetadata.isValid()) {
|
||||
tool.version = updatedMetadata.version;
|
||||
if (tool.version.empty() || tool.version == "-") {
|
||||
tool.version = "installed";
|
||||
}
|
||||
@ -621,38 +721,73 @@ int hash_command(int argc, char* argv[]) {
|
||||
|
||||
int unpublish_tool(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: getpkg unpublish <tool_name[:ARCH]>" << std::endl;
|
||||
std::cerr << "Usage: getpkg unpublish [--server <url>] <tool_name[:ARCH]>" << std::endl;
|
||||
std::cerr << " getpkg unpublish [--server <url>] <hash>" << std::endl;
|
||||
std::cerr << " getpkg unpublish <tool_name[:ARCH]>" << std::endl;
|
||||
std::cerr << " getpkg unpublish <hash>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string target = argv[2];
|
||||
|
||||
// Get token
|
||||
std::string token;
|
||||
const char* envToken = std::getenv("SOS_WRITE_TOKEN");
|
||||
if (envToken && std::strlen(envToken) > 0) {
|
||||
token = envToken;
|
||||
} else {
|
||||
std::string home = get_home();
|
||||
std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt";
|
||||
if (std::filesystem::exists(tokenPath)) {
|
||||
std::ifstream tfile(tokenPath);
|
||||
std::getline(tfile, token);
|
||||
} else {
|
||||
std::cout << "Enter getpkg.xyz write token: ";
|
||||
std::getline(std::cin, token);
|
||||
std::filesystem::create_directories(tokenPath.parent_path());
|
||||
std::ofstream tfile(tokenPath);
|
||||
tfile << token << std::endl;
|
||||
// Parse arguments for --server option
|
||||
std::string targetServer;
|
||||
std::string target;
|
||||
|
||||
if (argc >= 4 && std::string(argv[2]) == "--server") {
|
||||
if (argc < 5) {
|
||||
std::cerr << "Usage: getpkg unpublish --server <url> <tool_name[:ARCH]|hash>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
targetServer = argv[3];
|
||||
target = argv[4];
|
||||
} else {
|
||||
target = argv[2];
|
||||
}
|
||||
|
||||
if (token.empty()) {
|
||||
std::cerr << "Error: No write token provided" << std::endl;
|
||||
// Initialize ServerManager
|
||||
ServerManager serverManager;
|
||||
if (!serverManager.loadConfiguration()) {
|
||||
std::cerr << "Failed to load server configuration" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
GetbinClient getbin;
|
||||
// Determine target server
|
||||
std::string unpublishServer;
|
||||
if (!targetServer.empty()) {
|
||||
// User specified a server, validate it exists in configuration
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
if (std::find(servers.begin(), servers.end(), targetServer) == servers.end()) {
|
||||
std::cerr << "Error: Server '" << targetServer << "' is not configured" << std::endl;
|
||||
std::cerr << "Use 'getpkg server add " << targetServer << "' to add it first" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
unpublishServer = targetServer;
|
||||
} else {
|
||||
// Use default publish server (first server with write token)
|
||||
unpublishServer = serverManager.getDefaultPublishServer();
|
||||
if (unpublishServer.empty()) {
|
||||
std::cerr << "Error: No servers with write tokens configured" << std::endl;
|
||||
std::cerr << "Use 'getpkg server add <url>' and provide a write token" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Get write token for the target server
|
||||
std::string token = serverManager.getWriteToken(unpublishServer);
|
||||
if (token.empty()) {
|
||||
// Check environment variable as fallback
|
||||
const char* envToken = std::getenv("SOS_WRITE_TOKEN");
|
||||
if (envToken && std::strlen(envToken) > 0) {
|
||||
token = envToken;
|
||||
} else {
|
||||
std::cerr << "Error: No write token found for server '" << unpublishServer << "'" << std::endl;
|
||||
std::cerr << "Set SOS_WRITE_TOKEN environment variable or configure token for this server" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize GetbinClient with server list
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
GetbinClient getbin(servers);
|
||||
std::string hash = target;
|
||||
|
||||
// Check if target looks like a hash (all digits) or a tool name
|
||||
@ -677,8 +812,8 @@ int unpublish_tool(int argc, char* argv[]) {
|
||||
|
||||
// If a specific architecture was requested, only unpublish that one
|
||||
if (!specificArch.empty()) {
|
||||
if (!getbin.getHash(toolName, specificArch, hash)) {
|
||||
std::cerr << "Failed to get hash for " << target << std::endl;
|
||||
if (!getbin.getHash(unpublishServer, toolName, specificArch, hash)) {
|
||||
std::cerr << "Failed to get hash for " << target << " on server " << unpublishServer << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -702,14 +837,14 @@ int unpublish_tool(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Found hash " << hash << " for " << target << std::endl;
|
||||
std::cout << "Found hash " << hash << " for " << target << " on " << unpublishServer << std::endl;
|
||||
|
||||
// Delete the specific architecture
|
||||
if (getbin.deleteObject(hash, token)) {
|
||||
std::cout << "Successfully unpublished " << target << " (hash: " << hash << ")" << std::endl;
|
||||
std::cout << "Successfully unpublished " << target << " from " << unpublishServer << " (hash: " << hash << ")" << std::endl;
|
||||
return 0;
|
||||
} else {
|
||||
std::cerr << "Failed to unpublish " << target << std::endl;
|
||||
std::cerr << "Failed to unpublish " << target << " from " << unpublishServer << std::endl;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
@ -1060,10 +1195,12 @@ int uninstall_tool(int argc, char* argv[]) {
|
||||
std::string home = get_home();
|
||||
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
|
||||
std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName;
|
||||
std::filesystem::path toolInfoPath = configDir / (toolName + ".json");
|
||||
|
||||
// Check if tool is installed
|
||||
if (!std::filesystem::exists(toolInfoPath)) {
|
||||
// Initialize PackageMetadataManager
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
|
||||
// Check if tool is installed using PackageMetadataManager
|
||||
if (!packageManager.packageExists(toolName)) {
|
||||
std::cerr << "Tool " << toolName << " is not installed." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
@ -1080,10 +1217,8 @@ int uninstall_tool(int argc, char* argv[]) {
|
||||
std::filesystem::remove_all(binDir);
|
||||
}
|
||||
|
||||
// Remove tool info file
|
||||
if (std::filesystem::exists(toolInfoPath)) {
|
||||
std::filesystem::remove(toolInfoPath);
|
||||
}
|
||||
// Remove tool metadata
|
||||
packageManager.removePackageMetadata(toolName);
|
||||
|
||||
std::cout << "Uninstalled " << toolName << " successfully." << std::endl;
|
||||
return 0;
|
||||
@ -1104,14 +1239,15 @@ void show_help() {
|
||||
std::cout << " uninstall <tool_name> Remove an installed tool" << std::endl;
|
||||
std::cout << " Removes tool files, PATH entries, and autocomplete" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " publish <tool_name[:ARCH]> <folder> Upload a tool to getpkg.xyz" << std::endl;
|
||||
std::cout << " publish [--server <url>] <tool_name[:ARCH]> <folder>" << std::endl;
|
||||
std::cout << " Upload a tool to a package server" << std::endl;
|
||||
std::cout << " ARCH is optional (defaults to 'universal')" << std::endl;
|
||||
std::cout << " Requires SOS_WRITE_TOKEN environment variable" << std::endl;
|
||||
std::cout << " Uses default publish server if --server not specified" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " unpublish <tool_name> Remove ALL architectures of a tool" << std::endl;
|
||||
std::cout << " unpublish <tool_name:ARCH> Remove specific architecture only" << std::endl;
|
||||
std::cout << " unpublish <hash> Remove a tool by hash" << std::endl;
|
||||
std::cout << " Requires SOS_WRITE_TOKEN environment variable" << std::endl;
|
||||
std::cout << " unpublish [--server <url>] <tool_name> Remove ALL architectures of a tool" << std::endl;
|
||||
std::cout << " unpublish [--server <url>] <tool_name:ARCH> Remove specific architecture only" << std::endl;
|
||||
std::cout << " unpublish [--server <url>] <hash> Remove a tool by hash" << std::endl;
|
||||
std::cout << " Uses default publish server if --server not specified" << std::endl;
|
||||
std::cout << " Without :ARCH, removes x86_64, aarch64, and universal versions" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " update Update getpkg and all installed tools" << std::endl;
|
||||
@ -1128,6 +1264,15 @@ void show_help() {
|
||||
std::cout << " clean Clean up orphaned configs and symlinks" << std::endl;
|
||||
std::cout << " Removes unused config files and dangling symlinks" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " server add <url> Add a new package server" << std::endl;
|
||||
std::cout << " Adds a server to the configuration for package discovery" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " server remove <url> Remove a package server" << std::endl;
|
||||
std::cout << " Removes a server from the configuration" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " server list List all configured servers" << std::endl;
|
||||
std::cout << " Shows all servers with their status and write token info" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " version Show getpkg version" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << " help Show this help message" << std::endl;
|
||||
@ -1137,10 +1282,15 @@ void show_help() {
|
||||
std::cout << " getpkg install myapp Install myapp" << std::endl;
|
||||
std::cout << " getpkg publish myapp:x86_64 ./build Publish architecture-specific build" << std::endl;
|
||||
std::cout << " getpkg publish myapp ./build Publish universal build" << std::endl;
|
||||
std::cout << " getpkg publish --server example.com myapp ./build Publish to specific server" << std::endl;
|
||||
std::cout << " getpkg unpublish myapp Remove ALL architectures of myapp" << std::endl;
|
||||
std::cout << " getpkg unpublish myapp:x86_64 Remove only x86_64 version" << std::endl;
|
||||
std::cout << " getpkg unpublish --server example.com myapp Remove from specific server" << std::endl;
|
||||
std::cout << " getpkg uninstall myapp Remove myapp from system" << std::endl;
|
||||
std::cout << " getpkg update Update everything" << std::endl;
|
||||
std::cout << " getpkg server add packages.example.com Add a custom package server" << std::endl;
|
||||
std::cout << " getpkg server remove packages.example.com Remove a package server" << std::endl;
|
||||
std::cout << " getpkg server list List all configured servers" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "ENVIRONMENT:" << std::endl;
|
||||
std::cout << " SOS_WRITE_TOKEN Auth token for publishing tools" << std::endl;
|
||||
@ -1151,6 +1301,163 @@ void show_help() {
|
||||
std::cout << " ~/.local/bin/getpkg/ Installed tool binaries" << std::endl;
|
||||
}
|
||||
|
||||
int server_command(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: getpkg server <add|remove|list> [args...]" << std::endl;
|
||||
std::cerr << " getpkg server add <url> Add a new server" << std::endl;
|
||||
std::cerr << " getpkg server remove <url> Remove a server" << std::endl;
|
||||
std::cerr << " getpkg server list List all configured servers" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string subcommand = argv[2];
|
||||
ServerManager serverManager;
|
||||
|
||||
// Load existing configuration
|
||||
if (!serverManager.loadConfiguration()) {
|
||||
std::cerr << "Failed to load server configuration" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (subcommand == "add") {
|
||||
if (argc < 4) {
|
||||
std::cerr << "Usage: getpkg server add <url>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string serverUrl = argv[3];
|
||||
|
||||
// Validate server URL format
|
||||
if (serverUrl.empty()) {
|
||||
std::cerr << "Error: Server URL cannot be empty" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Remove protocol if provided (we'll add it internally)
|
||||
if (serverUrl.find("http://") == 0) {
|
||||
serverUrl = serverUrl.substr(7);
|
||||
} else if (serverUrl.find("https://") == 0) {
|
||||
serverUrl = serverUrl.substr(8);
|
||||
}
|
||||
|
||||
// Remove trailing slash if present
|
||||
if (!serverUrl.empty() && serverUrl.back() == '/') {
|
||||
serverUrl.pop_back();
|
||||
}
|
||||
|
||||
std::cout << "Adding server: " << serverUrl << std::endl;
|
||||
|
||||
if (serverManager.addServer(serverUrl)) {
|
||||
std::cout << "Successfully added server: " << serverUrl << std::endl;
|
||||
|
||||
// Ask if user wants to add a write token
|
||||
std::cout << "Would you like to add a write token for this server? (y/N): ";
|
||||
std::string response;
|
||||
std::getline(std::cin, response);
|
||||
|
||||
if (response == "y" || response == "Y" || response == "yes" || response == "Yes") {
|
||||
std::cout << "Enter write token for " << serverUrl << ": ";
|
||||
std::string token;
|
||||
std::getline(std::cin, token);
|
||||
|
||||
if (!token.empty()) {
|
||||
if (serverManager.setWriteToken(serverUrl, token)) {
|
||||
std::cout << "Write token added successfully" << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to save write token" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
std::cerr << "Failed to add server: " << serverUrl << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
} else if (subcommand == "remove") {
|
||||
if (argc < 4) {
|
||||
std::cerr << "Usage: getpkg server remove <url>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string serverUrl = argv[3];
|
||||
|
||||
// Remove protocol if provided
|
||||
if (serverUrl.find("http://") == 0) {
|
||||
serverUrl = serverUrl.substr(7);
|
||||
} else if (serverUrl.find("https://") == 0) {
|
||||
serverUrl = serverUrl.substr(8);
|
||||
}
|
||||
|
||||
// Remove trailing slash if present
|
||||
if (!serverUrl.empty() && serverUrl.back() == '/') {
|
||||
serverUrl.pop_back();
|
||||
}
|
||||
|
||||
std::cout << "Removing server: " << serverUrl << std::endl;
|
||||
|
||||
if (serverManager.removeServer(serverUrl)) {
|
||||
std::cout << "Successfully removed server: " << serverUrl << std::endl;
|
||||
return 0;
|
||||
} else {
|
||||
std::cerr << "Failed to remove server: " << serverUrl << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
} else if (subcommand == "list") {
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
|
||||
if (servers.empty()) {
|
||||
std::cout << "No servers configured" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Configured servers:" << std::endl;
|
||||
std::cout << "+" << std::string(30, '-') << "+" << std::string(12, '-') << "+" << std::string(15, '-') << "+" << std::endl;
|
||||
std::cout << "|" << std::setw(30) << std::left << " Server URL"
|
||||
<< "|" << std::setw(12) << std::left << " Default"
|
||||
<< "|" << std::setw(15) << std::left << " Write Token"
|
||||
<< "|" << std::endl;
|
||||
std::cout << "+" << std::string(30, '-') << "+" << std::string(12, '-') << "+" << std::string(15, '-') << "+" << std::endl;
|
||||
|
||||
std::string defaultServer = serverManager.getDefaultServer();
|
||||
|
||||
for (const auto& server : servers) {
|
||||
bool isDefault = (server == defaultServer);
|
||||
bool hasToken = serverManager.hasWriteToken(server);
|
||||
|
||||
std::string displayUrl = server;
|
||||
if (displayUrl.length() > 29) {
|
||||
displayUrl = displayUrl.substr(0, 26) + "...";
|
||||
}
|
||||
|
||||
std::cout << "|" << std::setw(30) << std::left << (" " + displayUrl)
|
||||
<< "|" << std::setw(12) << std::left << (isDefault ? " Yes" : " No")
|
||||
<< "|" << std::setw(15) << std::left << (hasToken ? " Yes" : " No")
|
||||
<< "|" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "+" << std::string(30, '-') << "+" << std::string(12, '-') << "+" << std::string(15, '-') << "+" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Total servers: " << servers.size() << std::endl;
|
||||
|
||||
// Show default publish server if different from default
|
||||
std::string defaultPublishServer = serverManager.getDefaultPublishServer();
|
||||
if (defaultPublishServer != defaultServer) {
|
||||
std::cout << "Default publish server: " << defaultPublishServer << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
std::cerr << "Unknown server subcommand: " << subcommand << std::endl;
|
||||
std::cerr << "Use 'getpkg server' for usage information." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int autocomplete_command(int argc, char* argv[]) {
|
||||
std::vector<std::string> args(argv + 2, argv + argc);
|
||||
|
||||
@ -1166,6 +1473,7 @@ int autocomplete_command(int argc, char* argv[]) {
|
||||
std::cout << "hash\n";
|
||||
std::cout << "list\n";
|
||||
std::cout << "clean\n";
|
||||
std::cout << "server\n";
|
||||
std::cout << "help\n";
|
||||
return 0;
|
||||
}
|
||||
@ -1180,6 +1488,35 @@ int autocomplete_command(int argc, char* argv[]) {
|
||||
} else if (subcommand == "uninstall") {
|
||||
// For uninstall, list installed tools
|
||||
std::filesystem::path configDir = std::filesystem::path(std::getenv("HOME")) / ".config" / "getpkg";
|
||||
if (std::filesystem::exists(configDir)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
std::cout << entry.path().stem().string() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else if (subcommand == "server") {
|
||||
// Handle server subcommand autocompletion
|
||||
if (args.size() == 1) {
|
||||
// Show server subcommands
|
||||
std::cout << "add\n";
|
||||
std::cout << "remove\n";
|
||||
std::cout << "list\n";
|
||||
} else if (args.size() == 2 && args[1] == "remove") {
|
||||
// For server remove, list configured servers
|
||||
ServerManager serverManager;
|
||||
if (serverManager.loadConfiguration()) {
|
||||
std::vector<std::string> servers = serverManager.getServers();
|
||||
for (const auto& server : servers) {
|
||||
std::cout << server << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else if (subcommand == "unpublish") {
|
||||
// For unpublish, we could suggest installed tools
|
||||
std::filesystem::path configDir = std::filesystem::path(std::getenv("HOME")) / ".config" / "getpkg";
|
||||
if (std::filesystem::exists(configDir)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
@ -1327,6 +1664,8 @@ int main(int argc, char* argv[]) {
|
||||
return list_packages(argc, argv);
|
||||
} else if (command == "clean") {
|
||||
return clean_tool(argc, argv);
|
||||
} else if (command == "server") {
|
||||
return server_command(argc, argv);
|
||||
} else if (command == "help") {
|
||||
show_help();
|
||||
} else {
|
||||
|
@ -44,8 +44,8 @@ cleanup() {
|
||||
|
||||
# Remove local test directories
|
||||
rm -rf "$TEST_DIR"
|
||||
rm -rf ~/.config/getpkg/"${TEST_TOOL_NAME}.json" 2>/dev/null || true
|
||||
rm -rf ~/.config/getpkg/"${TEST_TOOL_NAME}-noarch.json" 2>/dev/null || true
|
||||
rm -rf ~/.config/getpkg/packages/"${TEST_TOOL_NAME}.json" 2>/dev/null || true
|
||||
rm -rf ~/.config/getpkg/packages/"${TEST_TOOL_NAME}-noarch.json" 2>/dev/null || true
|
||||
rm -rf ~/.getpkg/"${TEST_TOOL_NAME}" 2>/dev/null || true
|
||||
rm -rf ~/.getpkg/"${TEST_TOOL_NAME}-noarch" 2>/dev/null || true
|
||||
rm -rf ~/.local/bin/getpkg/"${TEST_TOOL_NAME}" 2>/dev/null || true
|
||||
@ -295,7 +295,7 @@ if [ -n "${SOS_WRITE_TOKEN:-}" ]; then
|
||||
echo "Publish command failed with no output. Checking for missing dependencies..."
|
||||
ldd "$GETPKG" 2>&1 | grep "not found" || echo "All dependencies found"
|
||||
fi
|
||||
if [[ "$PUBLISH_OUTPUT" =~ Published! ]] && [[ "$PUBLISH_OUTPUT" =~ URL: ]] && [[ "$PUBLISH_OUTPUT" =~ Hash: ]]; then
|
||||
if [[ "$PUBLISH_OUTPUT" =~ Published.*! ]] && [[ "$PUBLISH_OUTPUT" =~ URL: ]] && [[ "$PUBLISH_OUTPUT" =~ Hash: ]]; then
|
||||
print_test_result "Publish tool with ARCH to getpkg.xyz" 0
|
||||
|
||||
# Extract hash for later cleanup
|
||||
@ -305,8 +305,20 @@ if [ -n "${SOS_WRITE_TOKEN:-}" ]; then
|
||||
# Test 8: Check if published tool exists
|
||||
echo -e "\nTest 8: Check published tool exists"
|
||||
EXISTS_CHECK=$(curl -s "https://getpkg.xyz/exists/${TEST_TOOL_NAME}:${TEST_ARCH}" 2>/dev/null || echo "error")
|
||||
if [[ "$EXISTS_CHECK" != "error" ]] && [[ "$EXISTS_CHECK" != "false" ]]; then
|
||||
print_test_result "Published tool exists on server" 0
|
||||
# Parse JSON response to check if exists is true
|
||||
if [[ "$EXISTS_CHECK" != "error" ]]; then
|
||||
# Try to parse JSON response
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
EXISTS_VALUE=$(echo "$EXISTS_CHECK" | jq -r '.exists' 2>/dev/null || echo "false")
|
||||
else
|
||||
# Fallback: extract exists value using grep/sed
|
||||
EXISTS_VALUE=$(echo "$EXISTS_CHECK" | grep -o '"exists":[^,}]*' | sed 's/.*:\s*//' | tr -d ' ' || echo "false")
|
||||
fi
|
||||
if [[ "$EXISTS_VALUE" == "true" ]]; then
|
||||
print_test_result "Published tool exists on server" 0
|
||||
else
|
||||
print_test_result "Published tool exists on server" 1
|
||||
fi
|
||||
else
|
||||
print_test_result "Published tool exists on server" 1
|
||||
fi
|
||||
@ -319,7 +331,7 @@ if [ -n "${SOS_WRITE_TOKEN:-}" ]; then
|
||||
|
||||
# Test 10: Check installed files
|
||||
echo -e "\nTest 10: Check installed files"
|
||||
if [ -f ~/.config/getpkg/"${TEST_TOOL_NAME}.json" ] && [ -d ~/.getpkg/"${TEST_TOOL_NAME}" ] && [ -L ~/.local/bin/getpkg/"${TEST_TOOL_NAME}" ]; then
|
||||
if [ -f ~/.config/getpkg/packages/"${TEST_TOOL_NAME}.json" ] && [ -d ~/.getpkg/"${TEST_TOOL_NAME}" ] && [ -L ~/.local/bin/getpkg/"${TEST_TOOL_NAME}" ]; then
|
||||
print_test_result "Tool files installed correctly" 0
|
||||
else
|
||||
print_test_result "Tool files installed correctly" 1
|
||||
@ -338,7 +350,7 @@ if [ -n "${SOS_WRITE_TOKEN:-}" ]; then
|
||||
# First remove the tool
|
||||
rm -rf ~/.getpkg/"${TEST_TOOL_NAME}"
|
||||
rm -rf ~/.local/bin/getpkg/"${TEST_TOOL_NAME}"
|
||||
rm -f ~/.config/getpkg/"${TEST_TOOL_NAME}.json"
|
||||
rm -f ~/.config/getpkg/packages/"${TEST_TOOL_NAME}.json"
|
||||
|
||||
REINSTALL_OUTPUT=$(timeout 3 "$GETPKG" install "$TEST_TOOL_NAME" 2>&1) || REINSTALL_OUTPUT=""
|
||||
if [[ "$REINSTALL_OUTPUT" =~ Installed\ ${TEST_TOOL_NAME}\ successfully ]] || [[ "$REINSTALL_OUTPUT" =~ ${TEST_TOOL_NAME}\ is\ already\ up\ to\ date ]]; then
|
||||
@ -394,7 +406,7 @@ EOF
|
||||
chmod +x "${TEST_DIR}/${TEST_TOOL_NOARCH}/${TEST_TOOL_NOARCH}"
|
||||
|
||||
PUBLISH_NOARCH_OUTPUT=$(timeout 3 "$GETPKG" publish "${TEST_TOOL_NOARCH}" "${TEST_DIR}/${TEST_TOOL_NOARCH}" 2>&1) || PUBLISH_NOARCH_OUTPUT=""
|
||||
if [[ "$PUBLISH_NOARCH_OUTPUT" =~ Published! ]] && [[ "$PUBLISH_NOARCH_OUTPUT" =~ URL: ]] && [[ "$PUBLISH_NOARCH_OUTPUT" =~ Hash: ]]; then
|
||||
if [[ "$PUBLISH_NOARCH_OUTPUT" =~ Published.*! ]] && [[ "$PUBLISH_NOARCH_OUTPUT" =~ URL: ]] && [[ "$PUBLISH_NOARCH_OUTPUT" =~ Hash: ]]; then
|
||||
print_test_result "Publish tool without ARCH" 0
|
||||
else
|
||||
print_test_result "Publish tool without ARCH" 1
|
||||
@ -402,13 +414,13 @@ EOF
|
||||
|
||||
# Test 13c: Install universal tool (arch fallback)
|
||||
echo -e "\nTest 13c: Install universal tool (arch fallback)"
|
||||
rm -rf ~/.config/getpkg/"${TEST_TOOL_NOARCH}.json" ~/.getpkg/"${TEST_TOOL_NOARCH}" ~/.local/bin/getpkg/"${TEST_TOOL_NOARCH}" 2>/dev/null || true
|
||||
rm -rf ~/.config/getpkg/packages/"${TEST_TOOL_NOARCH}.json" ~/.getpkg/"${TEST_TOOL_NOARCH}" ~/.local/bin/getpkg/"${TEST_TOOL_NOARCH}" 2>/dev/null || true
|
||||
FALLBACK_INSTALL_OUTPUT=$(timeout 3 "$GETPKG" install "${TEST_TOOL_NOARCH}" 2>&1) || FALLBACK_INSTALL_OUTPUT=""
|
||||
|
||||
# Check if tool was installed successfully and has universal architecture
|
||||
if [[ "$FALLBACK_INSTALL_OUTPUT" =~ Installed\ ${TEST_TOOL_NOARCH}\ successfully ]] && [ -f ~/.config/getpkg/"${TEST_TOOL_NOARCH}.json" ]; then
|
||||
if [[ "$FALLBACK_INSTALL_OUTPUT" =~ Installed\ ${TEST_TOOL_NOARCH}\ successfully ]] && [ -f ~/.config/getpkg/packages/"${TEST_TOOL_NOARCH}.json" ]; then
|
||||
# Verify the architecture is "universal" in the config file
|
||||
INSTALLED_ARCH=$(grep -o '"arch"[[:space:]]*:[[:space:]]*"[^"]*"' ~/.config/getpkg/"${TEST_TOOL_NOARCH}.json" | sed 's/.*"\([^"]*\)".*/\1/')
|
||||
INSTALLED_ARCH=$(grep -o '"arch"[[:space:]]*:[[:space:]]*"[^"]*"' ~/.config/getpkg/packages/"${TEST_TOOL_NOARCH}.json" | sed 's/.*"\([^"]*\)".*/\1/')
|
||||
if [ "$INSTALLED_ARCH" = "universal" ]; then
|
||||
print_test_result "Install universal tool with arch fallback" 0
|
||||
|
||||
@ -429,10 +441,19 @@ EOF
|
||||
fi
|
||||
|
||||
# Clean up the noarch tool from server
|
||||
NOARCH_HASH=$(curl -s "https://getpkg.xyz/hash/${TEST_TOOL_NOARCH}" 2>/dev/null || echo "")
|
||||
if [ -n "$NOARCH_HASH" ] && [ "$NOARCH_HASH" != "null" ] && [ "$NOARCH_HASH" != "Not found" ]; then
|
||||
curl -s -H "Authorization: Bearer ${SOS_WRITE_TOKEN}" \
|
||||
"https://getpkg.xyz/deleteobject?hash=${NOARCH_HASH}" >/dev/null 2>&1 || true
|
||||
NOARCH_HASH_RESPONSE=$(curl -s "https://getpkg.xyz/hash/${TEST_TOOL_NOARCH}" 2>/dev/null || echo "")
|
||||
if [ -n "$NOARCH_HASH_RESPONSE" ]; then
|
||||
# Parse JSON response to extract hash
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
NOARCH_HASH=$(echo "$NOARCH_HASH_RESPONSE" | jq -r '.hash' 2>/dev/null || echo "")
|
||||
else
|
||||
# Fallback: extract hash value using grep/sed
|
||||
NOARCH_HASH=$(echo "$NOARCH_HASH_RESPONSE" | grep -o '"hash":"[^"]*"' | sed 's/.*"hash":"\([^"]*\)".*/\1/' || echo "")
|
||||
fi
|
||||
if [ -n "$NOARCH_HASH" ] && [ "$NOARCH_HASH" != "null" ]; then
|
||||
curl -s -H "Authorization: Bearer ${SOS_WRITE_TOKEN}" \
|
||||
"https://getpkg.xyz/deleteobject?hash=${NOARCH_HASH}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "\n${YELLOW}Skipping publish/install tests (SOS_WRITE_TOKEN not set)${NC}"
|
||||
@ -466,7 +487,7 @@ EOF
|
||||
|
||||
# Publish and install the tool
|
||||
PUBLISH_OUTPUT=$(timeout 3 "$GETPKG" publish "${TEST_UNINSTALL_TOOL}:${TEST_ARCH}" "$UNINSTALL_DIR" 2>&1)
|
||||
if [[ "$PUBLISH_OUTPUT" =~ Published! ]]; then
|
||||
if [[ "$PUBLISH_OUTPUT" =~ Published.*! ]]; then
|
||||
INSTALL_OUTPUT=$(timeout 3 "$GETPKG" install "$TEST_UNINSTALL_TOOL" 2>&1)
|
||||
if [[ "$INSTALL_OUTPUT" =~ Installed\ ${TEST_UNINSTALL_TOOL}\ successfully ]]; then
|
||||
# Count bashrc entries before uninstall
|
||||
@ -479,7 +500,7 @@ EOF
|
||||
SYMLINK_EXISTS=false
|
||||
# HELPER_SYMLINK_EXISTS=false
|
||||
|
||||
[ -f ~/.config/getpkg/"${TEST_UNINSTALL_TOOL}.json" ] && CONFIG_EXISTS=true
|
||||
[ -f ~/.config/getpkg/packages/"${TEST_UNINSTALL_TOOL}.json" ] && CONFIG_EXISTS=true
|
||||
[ -d ~/.getpkg/"$TEST_UNINSTALL_TOOL" ] && TOOL_DIR_EXISTS=true
|
||||
[ -L ~/.local/bin/getpkg/"$TEST_UNINSTALL_TOOL" ] && SYMLINK_EXISTS=true
|
||||
# Check if helper symlink exists (not currently used in validation)
|
||||
@ -493,7 +514,7 @@ EOF
|
||||
ALL_REMOVED=true
|
||||
|
||||
# Check config file removed
|
||||
if [ -f ~/.config/getpkg/"${TEST_UNINSTALL_TOOL}.json" ]; then
|
||||
if [ -f ~/.config/getpkg/packages/"${TEST_UNINSTALL_TOOL}.json" ]; then
|
||||
echo "ERROR: Config file still exists after uninstall"
|
||||
ALL_REMOVED=false
|
||||
fi
|
||||
@ -546,7 +567,7 @@ EOF
|
||||
fi
|
||||
|
||||
# Always cleanup test uninstall tool from server, even if test failed
|
||||
if [[ "$PUBLISH_OUTPUT" =~ Published! ]]; then
|
||||
if [[ "$PUBLISH_OUTPUT" =~ Published.*! ]]; then
|
||||
$GETPKG unpublish "${TEST_UNINSTALL_TOOL}:${TEST_ARCH}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
@ -577,7 +598,7 @@ echo "Multi-arch unpublish test"' > "$UNPUBLISH_TEST_DIR/$UNPUBLISH_TOOL_MULTI"
|
||||
PUBLISH_aarch64_OUTPUT=$("$GETPKG" publish "${UNPUBLISH_TOOL_MULTI}:aarch64" "$UNPUBLISH_TEST_DIR" 2>&1)
|
||||
PUBLISH_universal_OUTPUT=$("$GETPKG" publish "${UNPUBLISH_TOOL_MULTI}:universal" "$UNPUBLISH_TEST_DIR" 2>&1)
|
||||
|
||||
if [[ "$PUBLISH_x86_64_OUTPUT" =~ Published! ]] && [[ "$PUBLISH_aarch64_OUTPUT" =~ Published! ]] && [[ "$PUBLISH_universal_OUTPUT" =~ Published! ]]; then
|
||||
if [[ "$PUBLISH_x86_64_OUTPUT" =~ Published.*! ]] && [[ "$PUBLISH_aarch64_OUTPUT" =~ Published.*! ]] && [[ "$PUBLISH_universal_OUTPUT" =~ Published.*! ]]; then
|
||||
# Test robust unpublish - should remove ALL architectures
|
||||
sleep 1 # Give server time to process all publishes
|
||||
UNPUBLISH_OUTPUT=$("$GETPKG" unpublish "$UNPUBLISH_TOOL_MULTI" 2>&1)
|
||||
@ -607,7 +628,7 @@ echo "Universal arch unpublish test"' > "$UNPUBLISH_TEST_DIR/$UNPUBLISH_TOOL_CUS
|
||||
# Publish with universal architecture
|
||||
PUBLISH_CUSTOM_OUTPUT=$("$GETPKG" publish "${UNPUBLISH_TOOL_CUSTOM}:universal" "$UNPUBLISH_TEST_DIR" 2>&1)
|
||||
|
||||
if [[ "$PUBLISH_CUSTOM_OUTPUT" =~ Published! ]]; then
|
||||
if [[ "$PUBLISH_CUSTOM_OUTPUT" =~ Published.*! ]]; then
|
||||
# Test that unpublish can find and remove custom tags
|
||||
UNPUBLISH_CUSTOM_OUTPUT=$("$GETPKG" unpublish "$UNPUBLISH_TOOL_CUSTOM" 2>&1)
|
||||
UNPUBLISH_CUSTOM_EXIT_CODE=$?
|
||||
|
45
getpkg/test/CMakeLists.txt
Normal file
45
getpkg/test/CMakeLists.txt
Normal file
@ -0,0 +1,45 @@
|
||||
# Unit Tests for getpkg multi-server support
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Test project setup
|
||||
project(getpkg_tests VERSION 1.0.0 LANGUAGES CXX)
|
||||
|
||||
# Build configuration
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-static")
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(CMAKE_PREFIX_PATH /usr/local)
|
||||
|
||||
# Find packages
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
# Add module path for FindCPRStatic
|
||||
list(APPEND CMAKE_MODULE_PATH "/usr/local/share/cmake/Modules")
|
||||
find_package(CPRStatic REQUIRED)
|
||||
|
||||
# Include directories
|
||||
include_directories(../src)
|
||||
include_directories(../src/common)
|
||||
|
||||
# Source files from main project (excluding main.cpp)
|
||||
file(GLOB_RECURSE MAIN_SOURCES "../src/*.cpp")
|
||||
list(REMOVE_ITEM MAIN_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../src/main.cpp")
|
||||
|
||||
# Test source files
|
||||
file(GLOB_RECURSE TEST_SOURCES "*.cpp")
|
||||
|
||||
# Create test executable
|
||||
add_executable(getpkg_tests ${MAIN_SOURCES} ${TEST_SOURCES})
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(getpkg_tests PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
cpr::cpr_static)
|
||||
|
||||
# Enable testing
|
||||
enable_testing()
|
||||
|
||||
# Add test
|
||||
add_test(NAME unit_tests COMMAND getpkg_tests)
|
14
getpkg/test/Dockerfile.test-build
Normal file
14
getpkg/test/Dockerfile.test-build
Normal file
@ -0,0 +1,14 @@
|
||||
FROM gitea.jde.nz/public/dropshell-build-base:latest
|
||||
|
||||
# Copy source files
|
||||
COPY . /app/
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app/test
|
||||
|
||||
# Build tests
|
||||
RUN cmake -G Ninja -S . -B ./build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local
|
||||
RUN cmake --build ./build
|
||||
|
||||
# Run tests
|
||||
CMD ["./build/getpkg_tests"]
|
37
getpkg/test/build_and_run_tests.sh
Executable file
37
getpkg/test/build_and_run_tests.sh
Executable file
@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo "Building and running unit tests for getpkg multi-server support..."
|
||||
|
||||
# Create a temporary Dockerfile for building tests
|
||||
cat > "$SCRIPT_DIR/Dockerfile.test-build" << 'EOF'
|
||||
FROM gitea.jde.nz/public/dropshell-build-base:latest
|
||||
|
||||
# Copy source files
|
||||
COPY . /app/
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app/test
|
||||
|
||||
# Build tests
|
||||
RUN cmake -G Ninja -S . -B ./build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local
|
||||
RUN cmake --build ./build
|
||||
|
||||
# Run tests
|
||||
CMD ["./build/getpkg_tests"]
|
||||
EOF
|
||||
|
||||
echo "Building test container..."
|
||||
docker build -t getpkg-test-build -f "$SCRIPT_DIR/Dockerfile.test-build" "$PROJECT_DIR"
|
||||
|
||||
echo "Running unit tests..."
|
||||
docker run --rm getpkg-test-build
|
||||
|
||||
echo "Cleaning up..."
|
||||
rm -f "$SCRIPT_DIR/Dockerfile.test-build"
|
||||
|
||||
echo "Unit tests completed!"
|
91
getpkg/test/test_framework.hpp
Normal file
91
getpkg/test/test_framework.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
|
||||
// Simple test framework
|
||||
class TestRunner {
|
||||
public:
|
||||
using TestFunction = std::function<void()>;
|
||||
|
||||
static TestRunner& instance() {
|
||||
static TestRunner runner;
|
||||
return runner;
|
||||
}
|
||||
|
||||
void addTest(const std::string& name, TestFunction test) {
|
||||
tests_.push_back({name, test});
|
||||
}
|
||||
|
||||
int runAllTests() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
std::cout << "Running " << tests_.size() << " tests...\n\n";
|
||||
|
||||
for (const auto& test : tests_) {
|
||||
try {
|
||||
test.second();
|
||||
std::cout << "[PASS] " << test.first << std::endl;
|
||||
passed++;
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << "[FAIL] " << test.first << " - " << e.what() << std::endl;
|
||||
failed++;
|
||||
} catch (...) {
|
||||
std::cout << "[FAIL] " << test.first << " - Unknown error" << std::endl;
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\nResults: " << passed << " passed, " << failed << " failed\n";
|
||||
return failed;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<std::string, TestFunction>> tests_;
|
||||
};
|
||||
|
||||
// Test assertion macros
|
||||
#define ASSERT_TRUE(condition) \
|
||||
if (!(condition)) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition); \
|
||||
}
|
||||
|
||||
#define ASSERT_FALSE(condition) \
|
||||
if (condition) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition " should be false"); \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected != actual"); \
|
||||
}
|
||||
|
||||
#define ASSERT_STR_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected '" + std::string(expected) + "' but got '" + std::string(actual) + "'"); \
|
||||
}
|
||||
|
||||
#define ASSERT_NOT_EMPTY(str) \
|
||||
if ((str).empty()) { \
|
||||
throw std::runtime_error("Assertion failed: string should not be empty"); \
|
||||
}
|
||||
|
||||
#define ASSERT_GE(actual, expected) \
|
||||
if ((actual) < (expected)) { \
|
||||
throw std::runtime_error("Assertion failed: expected " + std::to_string(actual) + " >= " + std::to_string(expected)); \
|
||||
}
|
||||
|
||||
// Test registration macro
|
||||
#define TEST(suite, name) \
|
||||
void test_##suite##_##name(); \
|
||||
struct TestRegistrar_##suite##_##name { \
|
||||
TestRegistrar_##suite##_##name() { \
|
||||
TestRunner::instance().addTest(#suite "::" #name, test_##suite##_##name); \
|
||||
} \
|
||||
}; \
|
||||
static TestRegistrar_##suite##_##name registrar_##suite##_##name; \
|
||||
void test_##suite##_##name()
|
343
getpkg/test/test_getbin_client.cpp
Normal file
343
getpkg/test/test_getbin_client.cpp
Normal file
@ -0,0 +1,343 @@
|
||||
#include "GetbinClient.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
// Test framework declarations from test_main.cpp
|
||||
class TestRunner {
|
||||
public:
|
||||
static TestRunner& instance();
|
||||
void addTest(const std::string& name, std::function<void()> test);
|
||||
};
|
||||
|
||||
#define ASSERT_TRUE(condition) \
|
||||
if (!(condition)) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition); \
|
||||
}
|
||||
|
||||
#define ASSERT_FALSE(condition) \
|
||||
if (condition) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition " should be false"); \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected != actual"); \
|
||||
}
|
||||
|
||||
#define ASSERT_STR_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected '" + std::string(expected) + "' but got '" + std::string(actual) + "'"); \
|
||||
}
|
||||
|
||||
#define ASSERT_NOT_EMPTY(str) \
|
||||
if ((str).empty()) { \
|
||||
throw std::runtime_error("Assertion failed: string should not be empty"); \
|
||||
}
|
||||
|
||||
#define TEST(name) \
|
||||
void test_GetbinClient_##name(); \
|
||||
void register_GetbinClient_##name() { \
|
||||
TestRunner::instance().addTest("GetbinClient::" #name, test_GetbinClient_##name); \
|
||||
} \
|
||||
void test_GetbinClient_##name()
|
||||
|
||||
// Test helper class for GetbinClient testing
|
||||
class GetbinClientTestHelper {
|
||||
public:
|
||||
static std::filesystem::path createTempDir() {
|
||||
auto tempDir = std::filesystem::temp_directory_path() / "getpkg_client_test" / std::to_string(std::time(nullptr));
|
||||
std::filesystem::create_directories(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
static void cleanupTempDir(const std::filesystem::path& dir) {
|
||||
if (std::filesystem::exists(dir)) {
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void createTestFile(const std::filesystem::path& path, const std::string& content) {
|
||||
std::ofstream file(path);
|
||||
file << content;
|
||||
file.close();
|
||||
}
|
||||
|
||||
static std::string readTestFile(const std::filesystem::path& path) {
|
||||
std::ifstream file(path);
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
return content;
|
||||
}
|
||||
|
||||
// Mock progress callback for testing
|
||||
static bool mockProgressCallback(size_t downloaded, size_t total) {
|
||||
return true; // Continue download
|
||||
}
|
||||
|
||||
static bool cancelProgressCallback(size_t downloaded, size_t total) {
|
||||
return false; // Cancel download
|
||||
}
|
||||
};
|
||||
|
||||
TEST(DefaultConstructor) {
|
||||
GetbinClient client;
|
||||
|
||||
// Should work with default server
|
||||
ASSERT_NOT_EMPTY(client.getLastError()); // Initially empty, but method should exist
|
||||
}
|
||||
|
||||
TEST(MultiServerConstructor) {
|
||||
std::vector<std::string> servers = {"server1.com", "server2.com", "server3.com"};
|
||||
GetbinClient client(servers);
|
||||
|
||||
// Should initialize with multiple servers
|
||||
ASSERT_NOT_EMPTY(client.getLastError()); // Method should exist
|
||||
}
|
||||
|
||||
TEST(EmptyServerList) {
|
||||
std::vector<std::string> emptyServers;
|
||||
GetbinClient client(emptyServers);
|
||||
|
||||
// Should handle empty server list gracefully
|
||||
ASSERT_NOT_EMPTY(client.getLastError());
|
||||
}
|
||||
|
||||
TEST(NetworkErrorClassification) {
|
||||
GetbinClient client;
|
||||
|
||||
// Test error message generation
|
||||
ASSERT_NOT_EMPTY(client.getNetworkErrorMessage(NetworkError::ConnectionFailed));
|
||||
ASSERT_NOT_EMPTY(client.getNetworkErrorMessage(NetworkError::Timeout));
|
||||
ASSERT_NOT_EMPTY(client.getNetworkErrorMessage(NetworkError::NotFound));
|
||||
ASSERT_NOT_EMPTY(client.getNetworkErrorMessage(NetworkError::Unauthorized));
|
||||
ASSERT_NOT_EMPTY(client.getNetworkErrorMessage(NetworkError::ServerError));
|
||||
}
|
||||
|
||||
TEST(UrlBuilding) {
|
||||
GetbinClient client;
|
||||
|
||||
// Test URL building with different server formats
|
||||
// Note: This tests internal functionality, so we test through public methods
|
||||
std::string hash;
|
||||
|
||||
// Test with different server URL formats
|
||||
bool result1 = client.getHash("http://server.com", "test-tool", "x86_64", hash);
|
||||
bool result2 = client.getHash("https://server.com", "test-tool", "x86_64", hash);
|
||||
bool result3 = client.getHash("server.com", "test-tool", "x86_64", hash);
|
||||
|
||||
// These will fail due to network, but should not crash
|
||||
ASSERT_FALSE(result1 || result2 || result3); // All should fail gracefully
|
||||
}
|
||||
|
||||
TEST(FindPackageServerWithMultipleServers) {
|
||||
std::vector<std::string> servers = {"server1.com", "server2.com", "server3.com"};
|
||||
GetbinClient client(servers);
|
||||
|
||||
std::string foundServer;
|
||||
bool found = client.findPackageServer("nonexistent-tool", "x86_64", foundServer);
|
||||
|
||||
// Should not find nonexistent package, but should not crash
|
||||
ASSERT_FALSE(found);
|
||||
ASSERT_TRUE(foundServer.empty());
|
||||
}
|
||||
|
||||
TEST(MultiServerDownloadFallback) {
|
||||
auto tempDir = GetbinClientTestHelper::createTempDir();
|
||||
auto outputPath = tempDir / "test-download";
|
||||
|
||||
std::vector<std::string> servers = {"invalid-server1.com", "invalid-server2.com", "invalid-server3.com"};
|
||||
GetbinClient client(servers);
|
||||
|
||||
// Should try all servers and fail gracefully
|
||||
bool result = client.download("nonexistent-tool", "x86_64", outputPath.string());
|
||||
ASSERT_FALSE(result);
|
||||
|
||||
// Should have error message
|
||||
ASSERT_NOT_EMPTY(client.getLastError());
|
||||
|
||||
GetbinClientTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ServerSpecificDownload) {
|
||||
auto tempDir = GetbinClientTestHelper::createTempDir();
|
||||
auto outputPath = tempDir / "test-download";
|
||||
|
||||
GetbinClient client;
|
||||
|
||||
// Test server-specific download with invalid server
|
||||
NetworkError error = client.downloadFromServer("invalid-server.com", "test-tool", "x86_64",
|
||||
outputPath.string());
|
||||
|
||||
// Should return appropriate network error
|
||||
ASSERT_TRUE(error != NetworkError::None);
|
||||
ASSERT_NOT_EMPTY(client.getNetworkErrorMessage(error));
|
||||
|
||||
GetbinClientTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ProgressCallbackHandling) {
|
||||
auto tempDir = GetbinClientTestHelper::createTempDir();
|
||||
auto outputPath = tempDir / "test-download";
|
||||
|
||||
GetbinClient client;
|
||||
|
||||
// Test with progress callback that continues
|
||||
bool result1 = client.download("test-tool", "x86_64", outputPath.string(),
|
||||
GetbinClientTestHelper::mockProgressCallback);
|
||||
ASSERT_FALSE(result1); // Will fail due to network, but should handle callback
|
||||
|
||||
// Test with progress callback that cancels
|
||||
bool result2 = client.download("test-tool", "x86_64", outputPath.string(),
|
||||
GetbinClientTestHelper::cancelProgressCallback);
|
||||
ASSERT_FALSE(result2); // Should handle cancellation
|
||||
|
||||
GetbinClientTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(HashRetrievalMultiServer) {
|
||||
std::vector<std::string> servers = {"server1.com", "server2.com"};
|
||||
GetbinClient client(servers);
|
||||
|
||||
std::string hash;
|
||||
|
||||
// Test multi-server hash retrieval
|
||||
bool result = client.getHash("test-tool", "x86_64", hash);
|
||||
ASSERT_FALSE(result); // Will fail due to network
|
||||
ASSERT_TRUE(hash.empty());
|
||||
|
||||
// Test server-specific hash retrieval
|
||||
bool result2 = client.getHash("server1.com", "test-tool", "x86_64", hash);
|
||||
ASSERT_FALSE(result2); // Will fail due to network
|
||||
ASSERT_TRUE(hash.empty());
|
||||
}
|
||||
|
||||
TEST(UploadFunctionality) {
|
||||
auto tempDir = GetbinClientTestHelper::createTempDir();
|
||||
auto testFile = tempDir / "test-archive.tar.gz";
|
||||
|
||||
// Create a test file
|
||||
GetbinClientTestHelper::createTestFile(testFile, "test archive content");
|
||||
|
||||
GetbinClient client;
|
||||
std::string outUrl, outHash;
|
||||
|
||||
// Test server-specific upload
|
||||
bool result1 = client.upload("test-server.com", testFile.string(), outUrl, outHash, "test-token");
|
||||
ASSERT_FALSE(result1); // Will fail due to network
|
||||
|
||||
// Test backward compatibility upload
|
||||
bool result2 = client.upload(testFile.string(), outUrl, outHash, "test-token");
|
||||
ASSERT_FALSE(result2); // Will fail due to network
|
||||
|
||||
GetbinClientTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(LegacyMethodsCompatibility) {
|
||||
GetbinClient client;
|
||||
|
||||
// Test legacy delete method
|
||||
bool deleteResult = client.deleteObject("test-hash", "test-token");
|
||||
ASSERT_FALSE(deleteResult); // Will fail due to network
|
||||
|
||||
// Test legacy list packages method
|
||||
std::vector<std::string> packages;
|
||||
bool listResult = client.listPackages(packages);
|
||||
ASSERT_FALSE(listResult); // Will fail due to network
|
||||
ASSERT_TRUE(packages.empty());
|
||||
|
||||
// Test legacy list all entries method
|
||||
std::vector<std::pair<std::string, std::vector<std::string>>> entries;
|
||||
bool entriesResult = client.listAllEntries(entries);
|
||||
ASSERT_FALSE(entriesResult); // Will fail due to network
|
||||
ASSERT_TRUE(entries.empty());
|
||||
}
|
||||
|
||||
TEST(ErrorMessagePersistence) {
|
||||
GetbinClient client;
|
||||
|
||||
// Trigger an error
|
||||
std::string hash;
|
||||
client.getHash("invalid-server.com", "test-tool", "x86_64", hash);
|
||||
|
||||
// Error message should be set
|
||||
std::string error1 = client.getLastError();
|
||||
ASSERT_NOT_EMPTY(error1);
|
||||
|
||||
// Trigger another error
|
||||
client.download("test-tool", "x86_64", "/invalid/path");
|
||||
|
||||
// Error message should be updated
|
||||
std::string error2 = client.getLastError();
|
||||
ASSERT_NOT_EMPTY(error2);
|
||||
|
||||
// Errors might be different depending on which fails first
|
||||
// But both should be non-empty
|
||||
}
|
||||
|
||||
TEST(UserAgentGeneration) {
|
||||
GetbinClient client;
|
||||
|
||||
// Test that user agent is properly set (indirect test through network calls)
|
||||
std::string hash;
|
||||
bool result = client.getHash("test-server.com", "test-tool", "x86_64", hash);
|
||||
|
||||
// Should fail due to network but not crash due to user agent issues
|
||||
ASSERT_FALSE(result);
|
||||
ASSERT_NOT_EMPTY(client.getLastError());
|
||||
}
|
||||
|
||||
TEST(ConcurrentOperations) {
|
||||
std::vector<std::string> servers = {"server1.com", "server2.com"};
|
||||
GetbinClient client(servers);
|
||||
|
||||
// Test that multiple operations can be performed without interference
|
||||
std::string hash1, hash2;
|
||||
|
||||
bool result1 = client.getHash("tool1", "x86_64", hash1);
|
||||
bool result2 = client.getHash("tool2", "aarch64", hash2);
|
||||
|
||||
// Both should fail gracefully without interfering with each other
|
||||
ASSERT_FALSE(result1);
|
||||
ASSERT_FALSE(result2);
|
||||
ASSERT_TRUE(hash1.empty());
|
||||
ASSERT_TRUE(hash2.empty());
|
||||
}
|
||||
|
||||
TEST(ArchitectureHandling) {
|
||||
GetbinClient client;
|
||||
|
||||
std::string hash;
|
||||
|
||||
// Test different architecture strings
|
||||
bool result1 = client.getHash("test-tool", "x86_64", hash);
|
||||
bool result2 = client.getHash("test-tool", "aarch64", hash);
|
||||
bool result3 = client.getHash("test-tool", "universal", hash);
|
||||
bool result4 = client.getHash("test-tool", "invalid-arch", hash);
|
||||
|
||||
// All should fail due to network but handle architectures properly
|
||||
ASSERT_FALSE(result1);
|
||||
ASSERT_FALSE(result2);
|
||||
ASSERT_FALSE(result3);
|
||||
ASSERT_FALSE(result4);
|
||||
}
|
||||
|
||||
// Registration function
|
||||
void registerGetbinClientTests() {
|
||||
register_GetbinClient_DefaultConstructor();
|
||||
register_GetbinClient_MultiServerConstructor();
|
||||
register_GetbinClient_EmptyServerList();
|
||||
register_GetbinClient_NetworkErrorClassification();
|
||||
register_GetbinClient_UrlBuilding();
|
||||
register_GetbinClient_FindPackageServerWithMultipleServers();
|
||||
register_GetbinClient_MultiServerDownloadFallback();
|
||||
register_GetbinClient_ServerSpecificDownload();
|
||||
register_GetbinClient_ProgressCallbackHandling();
|
||||
register_GetbinClient_HashRetrievalMultiServer();
|
||||
register_GetbinClient_UploadFunctionality();
|
||||
register_GetbinClient_LegacyMethodsCompatibility();
|
||||
register_GetbinClient_ErrorMessagePersistence();
|
||||
register_GetbinClient_UserAgentGeneration();
|
||||
register_GetbinClient_ConcurrentOperations();
|
||||
register_GetbinClient_ArchitectureHandling();
|
||||
}
|
530
getpkg/test/test_integration.cpp
Normal file
530
getpkg/test/test_integration.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
#include "ServerManager.hpp"
|
||||
#include "GetbinClient.hpp"
|
||||
#include "PackageMetadata.hpp"
|
||||
#include "MigrationManager.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// Test framework declarations from test_main.cpp
|
||||
class TestRunner {
|
||||
public:
|
||||
static TestRunner& instance();
|
||||
void addTest(const std::string& name, std::function<void()> test);
|
||||
};
|
||||
|
||||
#define ASSERT_TRUE(condition) \
|
||||
if (!(condition)) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition); \
|
||||
}
|
||||
|
||||
#define ASSERT_FALSE(condition) \
|
||||
if (condition) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition " should be false"); \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected != actual"); \
|
||||
}
|
||||
|
||||
#define ASSERT_STR_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected '" + std::string(expected) + "' but got '" + std::string(actual) + "'"); \
|
||||
}
|
||||
|
||||
#define ASSERT_NOT_EMPTY(str) \
|
||||
if ((str).empty()) { \
|
||||
throw std::runtime_error("Assertion failed: string should not be empty"); \
|
||||
}
|
||||
|
||||
#define ASSERT_GE(actual, expected) \
|
||||
if ((actual) < (expected)) { \
|
||||
throw std::runtime_error("Assertion failed: expected " + std::to_string(actual) + " >= " + std::to_string(expected)); \
|
||||
}
|
||||
|
||||
#define TEST(name) \
|
||||
void test_Integration_##name(); \
|
||||
void register_Integration_##name() { \
|
||||
TestRunner::instance().addTest("Integration::" #name, test_Integration_##name); \
|
||||
} \
|
||||
void test_Integration_##name()
|
||||
|
||||
// Test helper class for Integration testing
|
||||
class IntegrationTestHelper {
|
||||
public:
|
||||
static std::filesystem::path createTempDir() {
|
||||
auto tempDir = std::filesystem::temp_directory_path() / "getpkg_integration_test" / std::to_string(std::time(nullptr));
|
||||
std::filesystem::create_directories(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
static void cleanupTempDir(const std::filesystem::path& dir) {
|
||||
if (std::filesystem::exists(dir)) {
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void setupLegacyEnvironment(const std::filesystem::path& configDir) {
|
||||
// Create legacy token file
|
||||
auto legacyDir = configDir / "getpkg.xyz";
|
||||
std::filesystem::create_directories(legacyDir);
|
||||
|
||||
std::ofstream tokenFile(legacyDir / "write_token.txt");
|
||||
tokenFile << "legacy-integration-token";
|
||||
tokenFile.close();
|
||||
|
||||
// Create legacy package files
|
||||
nlohmann::json package1 = {
|
||||
{"name", "integration-tool1"},
|
||||
{"version", "2023.1201.1000"},
|
||||
{"hash", "legacy123hash456"},
|
||||
{"arch", "x86_64"}
|
||||
};
|
||||
|
||||
nlohmann::json package2 = {
|
||||
{"name", "integration-tool2"},
|
||||
{"version", "2023.1202.1100"},
|
||||
{"hash", "legacy789hash012"},
|
||||
{"arch", "aarch64"}
|
||||
};
|
||||
|
||||
std::ofstream package1File(configDir / "integration-tool1.json");
|
||||
package1File << package1.dump(2);
|
||||
package1File.close();
|
||||
|
||||
std::ofstream package2File(configDir / "integration-tool2.json");
|
||||
package2File << package2.dump(2);
|
||||
package2File.close();
|
||||
}
|
||||
|
||||
static void verifyNewFormatStructure(const std::filesystem::path& configDir) {
|
||||
ASSERT_TRUE(std::filesystem::exists(configDir / "servers.json"));
|
||||
ASSERT_TRUE(std::filesystem::exists(configDir / "packages"));
|
||||
ASSERT_TRUE(std::filesystem::is_directory(configDir / "packages"));
|
||||
}
|
||||
|
||||
static void setEnvironmentHome(const std::filesystem::path& homeDir) {
|
||||
setenv("HOME", homeDir.c_str(), 1);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(CompleteWorkflowFromLegacyToMultiServer) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
|
||||
// Set up legacy environment
|
||||
IntegrationTestHelper::setupLegacyEnvironment(configDir);
|
||||
IntegrationTestHelper::setEnvironmentHome(tempDir);
|
||||
|
||||
// Step 1: Migration Manager detects need for migration
|
||||
MigrationManager migrationManager(configDir);
|
||||
ASSERT_TRUE(migrationManager.needsMigration());
|
||||
|
||||
// Step 2: Perform migration
|
||||
bool migrationResult = migrationManager.performMigration();
|
||||
ASSERT_TRUE(migrationResult);
|
||||
|
||||
// Verify migration results
|
||||
auto result = migrationManager.getLastMigrationResult();
|
||||
ASSERT_TRUE(result.success);
|
||||
ASSERT_EQ(2, result.migratedPackages);
|
||||
ASSERT_TRUE(result.serverConfigMigrated);
|
||||
|
||||
// Step 3: ServerManager loads new configuration
|
||||
ServerManager serverManager;
|
||||
auto loadResult = serverManager.loadConfiguration();
|
||||
ASSERT_EQ(ServerManagerError::None, loadResult);
|
||||
|
||||
auto servers = serverManager.getServers();
|
||||
ASSERT_EQ(1, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
ASSERT_TRUE(serverManager.hasWriteToken("getpkg.xyz"));
|
||||
|
||||
// Step 4: Add additional servers
|
||||
auto addResult = serverManager.addServer("packages.example.com");
|
||||
ASSERT_EQ(ServerManagerError::None, addResult);
|
||||
|
||||
serverManager.setWriteToken("packages.example.com", "example-token");
|
||||
|
||||
// Step 5: GetbinClient uses multi-server configuration
|
||||
auto updatedServers = serverManager.getServers();
|
||||
GetbinClient client(updatedServers);
|
||||
|
||||
// Test multi-server operations (will fail due to network but should not crash)
|
||||
std::string hash;
|
||||
bool hashResult = client.getHash("test-tool", "x86_64", hash);
|
||||
ASSERT_FALSE(hashResult); // Expected to fail due to network
|
||||
ASSERT_NOT_EMPTY(client.getLastError());
|
||||
|
||||
// Step 6: PackageMetadataManager works with migrated data
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
|
||||
auto installedPackages = packageManager.listInstalledPackages();
|
||||
ASSERT_EQ(2, installedPackages.size());
|
||||
|
||||
// Verify migrated packages have server information
|
||||
PackageMetadata tool1 = packageManager.loadPackageMetadata("integration-tool1");
|
||||
ASSERT_TRUE(tool1.isValid());
|
||||
ASSERT_STR_EQ("getpkg.xyz", tool1.sourceServer);
|
||||
ASSERT_NOT_EMPTY(tool1.installDate);
|
||||
|
||||
// Step 7: Save new package with multi-server metadata
|
||||
PackageMetadata newPackage("new-tool", "2024.0115.1430", "new123hash456", "x86_64", "packages.example.com");
|
||||
bool saveResult = packageManager.savePackageMetadata(newPackage);
|
||||
ASSERT_TRUE(saveResult);
|
||||
|
||||
// Verify new package is tracked
|
||||
auto updatedPackages = packageManager.listInstalledPackages();
|
||||
ASSERT_EQ(3, updatedPackages.size());
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ServerManagerAndGetbinClientIntegration) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
IntegrationTestHelper::setEnvironmentHome(tempDir);
|
||||
|
||||
// Initialize ServerManager
|
||||
ServerManager serverManager;
|
||||
serverManager.ensureDefaultConfiguration();
|
||||
|
||||
// Add multiple servers
|
||||
serverManager.addServer("server1.example.com");
|
||||
serverManager.addServer("server2.example.com");
|
||||
serverManager.setWriteToken("server1.example.com", "token1");
|
||||
serverManager.setWriteToken("server2.example.com", "token2");
|
||||
|
||||
// Get server list for client
|
||||
auto servers = serverManager.getServers();
|
||||
ASSERT_EQ(3, servers.size()); // default + 2 added
|
||||
|
||||
// Initialize GetbinClient with server list
|
||||
GetbinClient client(servers);
|
||||
|
||||
// Test server-specific operations
|
||||
std::string foundServer;
|
||||
bool findResult = client.findPackageServer("test-tool", "x86_64", foundServer);
|
||||
ASSERT_FALSE(findResult); // Will fail due to network
|
||||
|
||||
// Test fallback behavior
|
||||
std::string hash;
|
||||
bool hashResult = client.getHash("test-tool", "x86_64", hash);
|
||||
ASSERT_FALSE(hashResult); // Will fail but should try all servers
|
||||
|
||||
// Verify error handling
|
||||
ASSERT_NOT_EMPTY(client.getLastError());
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(PackageMetadataAndMigrationIntegration) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
|
||||
// Create legacy package files
|
||||
nlohmann::json legacyPackage1 = {
|
||||
{"name", "metadata-tool1"},
|
||||
{"version", "1.0"},
|
||||
{"hash", "hash1"},
|
||||
{"arch", "x86_64"}
|
||||
};
|
||||
|
||||
nlohmann::json legacyPackage2 = {
|
||||
{"name", "metadata-tool2"},
|
||||
{"version", "2.0"},
|
||||
{"hash", "hash2"},
|
||||
{"arch", "aarch64"}
|
||||
};
|
||||
|
||||
std::ofstream file1(configDir / "metadata-tool1.json");
|
||||
file1 << legacyPackage1.dump(2);
|
||||
file1.close();
|
||||
|
||||
std::ofstream file2(configDir / "metadata-tool2.json");
|
||||
file2 << legacyPackage2.dump(2);
|
||||
file2.close();
|
||||
|
||||
// Use MigrationManager to migrate
|
||||
MigrationManager migrationManager(configDir);
|
||||
ASSERT_TRUE(migrationManager.needsMigration());
|
||||
|
||||
bool migrationResult = migrationManager.performMigration();
|
||||
ASSERT_TRUE(migrationResult);
|
||||
|
||||
// Use PackageMetadataManager to verify migration
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
|
||||
auto packages = packageManager.listInstalledPackages();
|
||||
ASSERT_EQ(2, packages.size());
|
||||
|
||||
// Verify migrated metadata
|
||||
PackageMetadata tool1 = packageManager.loadPackageMetadata("metadata-tool1");
|
||||
ASSERT_TRUE(tool1.isValid());
|
||||
ASSERT_STR_EQ("metadata-tool1", tool1.name);
|
||||
ASSERT_STR_EQ("1.0", tool1.version);
|
||||
ASSERT_STR_EQ("getpkg.xyz", tool1.sourceServer); // Should be set during migration
|
||||
ASSERT_NOT_EMPTY(tool1.installDate);
|
||||
|
||||
PackageMetadata tool2 = packageManager.loadPackageMetadata("metadata-tool2");
|
||||
ASSERT_TRUE(tool2.isValid());
|
||||
ASSERT_STR_EQ("aarch64", tool2.arch);
|
||||
ASSERT_STR_EQ("getpkg.xyz", tool2.sourceServer);
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(EndToEndPackageInstallationWorkflow) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
IntegrationTestHelper::setEnvironmentHome(tempDir);
|
||||
|
||||
// Step 1: Initialize ServerManager with multiple servers
|
||||
ServerManager serverManager;
|
||||
serverManager.ensureDefaultConfiguration();
|
||||
serverManager.addServer("primary.packages.com");
|
||||
serverManager.addServer("backup.packages.com");
|
||||
|
||||
// Step 2: Initialize GetbinClient with server list
|
||||
auto servers = serverManager.getServers();
|
||||
GetbinClient client(servers);
|
||||
|
||||
// Step 3: Initialize PackageMetadataManager
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
packageManager.ensurePackagesDirectory();
|
||||
|
||||
// Step 4: Simulate package installation workflow
|
||||
std::string toolName = "workflow-test-tool";
|
||||
std::string version = "2024.0115.1430";
|
||||
std::string hash = "workflow123hash456";
|
||||
std::string arch = "x86_64";
|
||||
std::string sourceServer = "primary.packages.com";
|
||||
|
||||
// Try to download (will fail due to network but tests the workflow)
|
||||
auto downloadPath = tempDir / "downloads" / (toolName + ".tar.gz");
|
||||
std::filesystem::create_directories(downloadPath.parent_path());
|
||||
|
||||
bool downloadResult = client.download(toolName, arch, downloadPath.string());
|
||||
ASSERT_FALSE(downloadResult); // Expected to fail due to network
|
||||
|
||||
// Simulate successful installation by creating metadata
|
||||
PackageMetadata metadata(toolName, version, hash, arch, sourceServer);
|
||||
bool saveResult = packageManager.savePackageMetadata(metadata);
|
||||
ASSERT_TRUE(saveResult);
|
||||
|
||||
// Step 5: Verify package is tracked
|
||||
ASSERT_TRUE(packageManager.packageExists(toolName));
|
||||
|
||||
PackageMetadata savedMetadata = packageManager.loadPackageMetadata(toolName);
|
||||
ASSERT_TRUE(savedMetadata.isValid());
|
||||
ASSERT_STR_EQ(sourceServer, savedMetadata.sourceServer);
|
||||
|
||||
// Step 6: Simulate update check
|
||||
bool needsUpdate = savedMetadata.needsUpdate("different-hash");
|
||||
ASSERT_TRUE(needsUpdate);
|
||||
|
||||
bool noUpdateNeeded = savedMetadata.needsUpdate(hash);
|
||||
ASSERT_FALSE(noUpdateNeeded);
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(MultiServerPublishingWorkflow) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
IntegrationTestHelper::setEnvironmentHome(tempDir);
|
||||
|
||||
// Initialize ServerManager with publishing tokens
|
||||
ServerManager serverManager;
|
||||
serverManager.ensureDefaultConfiguration();
|
||||
serverManager.addServer("publish1.example.com");
|
||||
serverManager.addServer("publish2.example.com");
|
||||
|
||||
serverManager.setWriteToken("getpkg.xyz", "default-token");
|
||||
serverManager.setWriteToken("publish1.example.com", "publish1-token");
|
||||
serverManager.setWriteToken("publish2.example.com", "publish2-token");
|
||||
|
||||
// Test default publish server selection
|
||||
std::string defaultPublishServer = serverManager.getDefaultPublishServer();
|
||||
ASSERT_STR_EQ("getpkg.xyz", defaultPublishServer); // First server with token
|
||||
|
||||
// Test servers with tokens
|
||||
auto serversWithTokens = serverManager.getServersWithTokens();
|
||||
ASSERT_EQ(3, serversWithTokens.size());
|
||||
|
||||
// Initialize GetbinClient for publishing
|
||||
auto servers = serverManager.getServers();
|
||||
GetbinClient client(servers);
|
||||
|
||||
// Create test archive for publishing
|
||||
auto testArchive = tempDir / "test-package.tar.gz";
|
||||
std::ofstream archiveFile(testArchive);
|
||||
archiveFile << "test archive content";
|
||||
archiveFile.close();
|
||||
|
||||
// Test server-specific publishing (will fail due to network)
|
||||
std::string outUrl, outHash;
|
||||
bool publishResult = client.upload("publish1.example.com", testArchive.string(),
|
||||
outUrl, outHash, "publish1-token");
|
||||
ASSERT_FALSE(publishResult); // Expected to fail due to network
|
||||
|
||||
// Test default publishing
|
||||
bool defaultPublishResult = client.upload(testArchive.string(), outUrl, outHash, "default-token");
|
||||
ASSERT_FALSE(defaultPublishResult); // Expected to fail due to network
|
||||
|
||||
// Verify error handling
|
||||
ASSERT_NOT_EMPTY(client.getLastError());
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ErrorHandlingAndRecoveryWorkflow) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
|
||||
// Create corrupted configuration
|
||||
std::ofstream corruptedConfig(configDir / "servers.json");
|
||||
corruptedConfig << "{ invalid json";
|
||||
corruptedConfig.close();
|
||||
|
||||
// ServerManager should recover from corruption
|
||||
ServerManager serverManager;
|
||||
auto loadResult = serverManager.loadConfiguration();
|
||||
ASSERT_EQ(ServerManagerError::None, loadResult); // Should recover
|
||||
|
||||
auto servers = serverManager.getServers();
|
||||
ASSERT_EQ(1, servers.size()); // Should have default server
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
|
||||
// Create corrupted package metadata
|
||||
std::filesystem::create_directories(configDir / "packages");
|
||||
std::ofstream corruptedPackage(configDir / "packages" / "corrupted-tool.json");
|
||||
corruptedPackage << "{ invalid json";
|
||||
corruptedPackage.close();
|
||||
|
||||
// PackageMetadataManager should handle corruption
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
|
||||
PackageMetadata corruptedMetadata = packageManager.loadPackageMetadata("corrupted-tool");
|
||||
ASSERT_FALSE(corruptedMetadata.isValid()); // Should fail gracefully
|
||||
|
||||
// Cleanup invalid metadata
|
||||
int cleanedCount = packageManager.cleanupInvalidMetadata();
|
||||
ASSERT_EQ(1, cleanedCount);
|
||||
|
||||
// Verify cleanup worked
|
||||
ASSERT_FALSE(packageManager.packageExists("corrupted-tool"));
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(BackwardCompatibilityWorkflow) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
IntegrationTestHelper::setEnvironmentHome(tempDir);
|
||||
|
||||
// Test that system works without any configuration (backward compatibility)
|
||||
ServerManager serverManager;
|
||||
serverManager.ensureDefaultConfiguration();
|
||||
|
||||
auto servers = serverManager.getServers();
|
||||
ASSERT_EQ(1, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
|
||||
// GetbinClient should work with default configuration
|
||||
GetbinClient defaultClient; // Default constructor
|
||||
|
||||
std::string hash;
|
||||
bool hashResult = defaultClient.getHash("test-tool", "x86_64", hash);
|
||||
ASSERT_FALSE(hashResult); // Will fail due to network
|
||||
|
||||
// Multi-server client should also work
|
||||
GetbinClient multiClient(servers);
|
||||
bool multiHashResult = multiClient.getHash("test-tool", "x86_64", hash);
|
||||
ASSERT_FALSE(multiHashResult); // Will fail due to network
|
||||
|
||||
// PackageMetadataManager should work with default structure
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
bool dirResult = packageManager.ensurePackagesDirectory();
|
||||
ASSERT_TRUE(dirResult);
|
||||
|
||||
// Should be able to save and load packages
|
||||
PackageMetadata testPackage("compat-tool", "1.0", "hash", "x86_64", "getpkg.xyz");
|
||||
bool saveResult = packageManager.savePackageMetadata(testPackage);
|
||||
ASSERT_TRUE(saveResult);
|
||||
|
||||
PackageMetadata loadedPackage = packageManager.loadPackageMetadata("compat-tool");
|
||||
ASSERT_TRUE(loadedPackage.isValid());
|
||||
ASSERT_STR_EQ("compat-tool", loadedPackage.name);
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ConcurrentOperationsWorkflow) {
|
||||
auto tempDir = IntegrationTestHelper::createTempDir();
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
IntegrationTestHelper::setEnvironmentHome(tempDir);
|
||||
|
||||
// Initialize components
|
||||
ServerManager serverManager;
|
||||
serverManager.ensureDefaultConfiguration();
|
||||
serverManager.addServer("concurrent1.example.com");
|
||||
serverManager.addServer("concurrent2.example.com");
|
||||
|
||||
auto servers = serverManager.getServers();
|
||||
GetbinClient client(servers);
|
||||
|
||||
PackageMetadataManager packageManager(configDir);
|
||||
packageManager.ensurePackagesDirectory();
|
||||
|
||||
// Simulate concurrent operations
|
||||
std::vector<std::string> tools = {"tool1", "tool2", "tool3", "tool4", "tool5"};
|
||||
|
||||
// Save multiple packages concurrently (simulated)
|
||||
for (const auto& tool : tools) {
|
||||
PackageMetadata metadata(tool, "1.0", "hash-" + tool, "x86_64", "concurrent1.example.com");
|
||||
bool saveResult = packageManager.savePackageMetadata(metadata);
|
||||
ASSERT_TRUE(saveResult);
|
||||
}
|
||||
|
||||
// Verify all packages were saved
|
||||
auto installedPackages = packageManager.listInstalledPackages();
|
||||
ASSERT_EQ(5, installedPackages.size());
|
||||
|
||||
// Test concurrent hash requests (will fail due to network but test concurrency)
|
||||
for (const auto& tool : tools) {
|
||||
std::string hash;
|
||||
bool hashResult = client.getHash(tool, "x86_64", hash);
|
||||
ASSERT_FALSE(hashResult); // Expected to fail
|
||||
}
|
||||
|
||||
// Verify no interference between operations
|
||||
for (const auto& tool : tools) {
|
||||
PackageMetadata metadata = packageManager.loadPackageMetadata(tool);
|
||||
ASSERT_TRUE(metadata.isValid());
|
||||
ASSERT_STR_EQ(tool, metadata.name);
|
||||
}
|
||||
|
||||
IntegrationTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
// Registration function
|
||||
void registerIntegrationTests() {
|
||||
register_Integration_CompleteWorkflowFromLegacyToMultiServer();
|
||||
register_Integration_ServerManagerAndGetbinClientIntegration();
|
||||
register_Integration_PackageMetadataAndMigrationIntegration();
|
||||
register_Integration_EndToEndPackageInstallationWorkflow();
|
||||
register_Integration_MultiServerPublishingWorkflow();
|
||||
register_Integration_ErrorHandlingAndRecoveryWorkflow();
|
||||
register_Integration_BackwardCompatibilityWorkflow();
|
||||
register_Integration_ConcurrentOperationsWorkflow();
|
||||
}
|
18
getpkg/test/test_main.cpp
Normal file
18
getpkg/test/test_main.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "test_framework.hpp"
|
||||
|
||||
int main() {
|
||||
std::cout << "=== getpkg Multi-Server Support Unit Tests ===" << std::endl;
|
||||
std::cout << "Testing all components for multi-server functionality" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Run all tests (automatically registered via static constructors)
|
||||
int result = TestRunner::instance().runAllTests();
|
||||
|
||||
if (result == 0) {
|
||||
std::cout << std::endl << "🎉 All tests passed! Multi-server support is working correctly." << std::endl;
|
||||
} else {
|
||||
std::cout << std::endl << "❌ Some tests failed. Please review the failures above." << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
545
getpkg/test/test_migration_manager.cpp
Normal file
545
getpkg/test/test_migration_manager.cpp
Normal file
@ -0,0 +1,545 @@
|
||||
#include "MigrationManager.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// Test framework declarations from test_main.cpp
|
||||
class TestRunner {
|
||||
public:
|
||||
static TestRunner& instance();
|
||||
void addTest(const std::string& name, std::function<void()> test);
|
||||
};
|
||||
|
||||
#define ASSERT_TRUE(condition) \
|
||||
if (!(condition)) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition); \
|
||||
}
|
||||
|
||||
#define ASSERT_FALSE(condition) \
|
||||
if (condition) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition " should be false"); \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected != actual"); \
|
||||
}
|
||||
|
||||
#define ASSERT_STR_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected '" + std::string(expected) + "' but got '" + std::string(actual) + "'"); \
|
||||
}
|
||||
|
||||
#define ASSERT_NOT_EMPTY(str) \
|
||||
if ((str).empty()) { \
|
||||
throw std::runtime_error("Assertion failed: string should not be empty"); \
|
||||
}
|
||||
|
||||
#define ASSERT_GE(actual, expected) \
|
||||
if ((actual) < (expected)) { \
|
||||
throw std::runtime_error("Assertion failed: expected " + std::to_string(actual) + " >= " + std::to_string(expected)); \
|
||||
}
|
||||
|
||||
#define TEST(name) \
|
||||
void test_MigrationManager_##name(); \
|
||||
void register_MigrationManager_##name() { \
|
||||
TestRunner::instance().addTest("MigrationManager::" #name, test_MigrationManager_##name); \
|
||||
} \
|
||||
void test_MigrationManager_##name()
|
||||
|
||||
// Test helper class for MigrationManager testing
|
||||
class MigrationManagerTestHelper {
|
||||
public:
|
||||
static std::filesystem::path createTempDir() {
|
||||
auto tempDir = std::filesystem::temp_directory_path() / "getpkg_migration_test" / std::to_string(std::time(nullptr));
|
||||
std::filesystem::create_directories(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
static void cleanupTempDir(const std::filesystem::path& dir) {
|
||||
if (std::filesystem::exists(dir)) {
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void createLegacyTokenFile(const std::filesystem::path& configDir, const std::string& token) {
|
||||
auto legacyDir = configDir / "getpkg.xyz";
|
||||
std::filesystem::create_directories(legacyDir);
|
||||
|
||||
std::ofstream tokenFile(legacyDir / "write_token.txt");
|
||||
tokenFile << token;
|
||||
tokenFile.close();
|
||||
}
|
||||
|
||||
static void createLegacyPackageFile(const std::filesystem::path& configDir, const std::string& toolName, const nlohmann::json& content) {
|
||||
std::ofstream packageFile(configDir / (toolName + ".json"));
|
||||
packageFile << content.dump(2);
|
||||
packageFile.close();
|
||||
}
|
||||
|
||||
static void createNewFormatConfig(const std::filesystem::path& configDir) {
|
||||
nlohmann::json config = {
|
||||
{"version", "1.0"},
|
||||
{"servers", {
|
||||
{
|
||||
{"url", "getpkg.xyz"},
|
||||
{"name", "Official getpkg Registry"},
|
||||
{"default", true},
|
||||
{"writeToken", ""},
|
||||
{"added", "2024-01-15T10:30:00Z"}
|
||||
}
|
||||
}},
|
||||
{"lastUpdated", "2024-01-15T10:30:00Z"}
|
||||
};
|
||||
|
||||
std::ofstream configFile(configDir / "servers.json");
|
||||
configFile << config.dump(2);
|
||||
configFile.close();
|
||||
}
|
||||
|
||||
static nlohmann::json createLegacyPackageJson(const std::string& name, const std::string& version, const std::string& hash, const std::string& arch) {
|
||||
return nlohmann::json{
|
||||
{"name", name},
|
||||
{"version", version},
|
||||
{"hash", hash},
|
||||
{"arch", arch}
|
||||
};
|
||||
}
|
||||
|
||||
static void createPackagesDirectory(const std::filesystem::path& configDir) {
|
||||
std::filesystem::create_directories(configDir / "packages");
|
||||
}
|
||||
|
||||
static bool fileExists(const std::filesystem::path& path) {
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
|
||||
static int countFilesInDirectory(const std::filesystem::path& dir, const std::string& extension = "") {
|
||||
if (!std::filesystem::exists(dir)) return 0;
|
||||
|
||||
int count = 0;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir)) {
|
||||
if (entry.is_regular_file()) {
|
||||
if (extension.empty() || entry.path().extension() == extension) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(DefaultConstructor) {
|
||||
MigrationManager manager;
|
||||
|
||||
// Should initialize without crashing
|
||||
// Actual functionality depends on environment setup
|
||||
}
|
||||
|
||||
TEST(CustomConstructor) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should initialize with custom config directory
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(NeedsMigrationNoLegacyData) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// No legacy data - should not need migration
|
||||
ASSERT_FALSE(manager.needsMigration());
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(NeedsMigrationWithLegacyToken) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create legacy token file
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "legacy-token-123");
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should need migration due to legacy token
|
||||
ASSERT_TRUE(manager.needsMigration());
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(NeedsMigrationWithLegacyPackages) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create legacy package files
|
||||
auto legacyPackage = MigrationManagerTestHelper::createLegacyPackageJson("test-tool", "1.0", "hash123", "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "test-tool", legacyPackage);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should need migration due to legacy packages
|
||||
ASSERT_TRUE(manager.needsMigration());
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(NeedsMigrationWithNewFormat) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create new format configuration
|
||||
MigrationManagerTestHelper::createNewFormatConfig(tempDir);
|
||||
MigrationManagerTestHelper::createPackagesDirectory(tempDir);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should not need migration - already in new format
|
||||
ASSERT_FALSE(manager.needsMigration());
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(MigrateServerConfigurationSuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create legacy token file
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "legacy-token-456");
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Migrate server configuration
|
||||
bool result = manager.migrateServerConfiguration();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Verify servers.json was created
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "servers.json"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(MigratePackageMetadataSuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create legacy package files
|
||||
auto package1 = MigrationManagerTestHelper::createLegacyPackageJson("tool1", "1.0", "hash1", "x86_64");
|
||||
auto package2 = MigrationManagerTestHelper::createLegacyPackageJson("tool2", "2.0", "hash2", "aarch64");
|
||||
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "tool1", package1);
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "tool2", package2);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Migrate package metadata
|
||||
bool result = manager.migratePackageMetadata();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Verify packages directory was created
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages"));
|
||||
|
||||
// Verify package files were moved and updated
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages" / "tool1.json"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages" / "tool2.json"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(CreatePackagesDirectorySuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Create packages directory
|
||||
bool result = manager.createPackagesDirectory();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Verify directory was created
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages"));
|
||||
ASSERT_TRUE(std::filesystem::is_directory(tempDir / "packages"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(PerformFullMigrationSuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Set up legacy environment
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "full-migration-token");
|
||||
|
||||
auto package1 = MigrationManagerTestHelper::createLegacyPackageJson("migrate-tool1", "1.0", "hash1", "x86_64");
|
||||
auto package2 = MigrationManagerTestHelper::createLegacyPackageJson("migrate-tool2", "2.0", "hash2", "aarch64");
|
||||
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "migrate-tool1", package1);
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "migrate-tool2", package2);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Verify migration is needed
|
||||
ASSERT_TRUE(manager.needsMigration());
|
||||
|
||||
// Perform full migration
|
||||
bool result = manager.performMigration();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Verify migration results
|
||||
auto migrationResult = manager.getLastMigrationResult();
|
||||
ASSERT_TRUE(migrationResult.success);
|
||||
ASSERT_EQ(2, migrationResult.migratedPackages);
|
||||
ASSERT_EQ(2, migrationResult.totalPackages);
|
||||
ASSERT_TRUE(migrationResult.serverConfigMigrated);
|
||||
ASSERT_TRUE(migrationResult.packageDirectoryCreated);
|
||||
|
||||
// Verify new format files exist
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "servers.json"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages" / "migrate-tool1.json"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages" / "migrate-tool2.json"));
|
||||
|
||||
// Verify no longer needs migration
|
||||
ASSERT_FALSE(manager.needsMigration());
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(PerformMigrationNoLegacyData) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should not need migration
|
||||
ASSERT_FALSE(manager.needsMigration());
|
||||
|
||||
// Migration should succeed but do nothing
|
||||
bool result = manager.performMigration();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
auto migrationResult = manager.getLastMigrationResult();
|
||||
ASSERT_TRUE(migrationResult.success);
|
||||
ASSERT_EQ(0, migrationResult.migratedPackages);
|
||||
ASSERT_EQ(0, migrationResult.totalPackages);
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ValidateMigrationSuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create proper new format structure
|
||||
MigrationManagerTestHelper::createNewFormatConfig(tempDir);
|
||||
MigrationManagerTestHelper::createPackagesDirectory(tempDir);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Validation should pass
|
||||
bool result = manager.validateMigration();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ValidateMigrationFailure) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create incomplete structure (missing packages directory)
|
||||
MigrationManagerTestHelper::createNewFormatConfig(tempDir);
|
||||
// Don't create packages directory
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Validation should fail
|
||||
bool result = manager.validateMigration();
|
||||
ASSERT_FALSE(result);
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(CreateBackupSuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create some files to backup
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "backup-token");
|
||||
auto package = MigrationManagerTestHelper::createLegacyPackageJson("backup-tool", "1.0", "hash", "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "backup-tool", package);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Create backup
|
||||
bool result = manager.createBackup();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Verify backup directory exists
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "migration_backup"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(RollbackCapability) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Set up legacy environment
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "rollback-token");
|
||||
auto package = MigrationManagerTestHelper::createLegacyPackageJson("rollback-tool", "1.0", "hash", "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "rollback-tool", package);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Initially should not be able to rollback
|
||||
ASSERT_FALSE(manager.canRollback());
|
||||
|
||||
// Create backup
|
||||
manager.createBackup();
|
||||
|
||||
// Now should be able to rollback
|
||||
ASSERT_TRUE(manager.canRollback());
|
||||
|
||||
// Perform migration
|
||||
manager.performMigration();
|
||||
|
||||
// Should still be able to rollback
|
||||
ASSERT_TRUE(manager.canRollback());
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(PerformRollbackSuccess) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Set up legacy environment
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "rollback-test-token");
|
||||
auto package = MigrationManagerTestHelper::createLegacyPackageJson("rollback-test-tool", "1.0", "hash", "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "rollback-test-tool", package);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Create backup and perform migration
|
||||
manager.createBackup();
|
||||
manager.performMigration();
|
||||
|
||||
// Verify migration completed
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "servers.json"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages"));
|
||||
|
||||
// Perform rollback
|
||||
bool rollbackResult = manager.performRollback();
|
||||
ASSERT_TRUE(rollbackResult);
|
||||
|
||||
// Verify rollback restored original state
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "getpkg.xyz" / "write_token.txt"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "rollback-test-tool.json"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(MigrationWithCorruptedLegacyData) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create corrupted legacy package file
|
||||
std::ofstream corruptedFile(tempDir / "corrupted-tool.json");
|
||||
corruptedFile << "{ invalid json content";
|
||||
corruptedFile.close();
|
||||
|
||||
// Create valid legacy data too
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "valid-token");
|
||||
auto validPackage = MigrationManagerTestHelper::createLegacyPackageJson("valid-tool", "1.0", "hash", "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "valid-tool", validPackage);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should still need migration
|
||||
ASSERT_TRUE(manager.needsMigration());
|
||||
|
||||
// Migration should handle corrupted data gracefully
|
||||
bool result = manager.performMigration();
|
||||
ASSERT_TRUE(result); // Should succeed despite corrupted data
|
||||
|
||||
auto migrationResult = manager.getLastMigrationResult();
|
||||
ASSERT_TRUE(migrationResult.success);
|
||||
ASSERT_GE(migrationResult.errors.size(), 0); // May have errors for corrupted data
|
||||
ASSERT_GE(migrationResult.warnings.size(), 0); // May have warnings
|
||||
|
||||
// Valid data should be migrated
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages" / "valid-tool.json"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(MigrationResultReporting) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Set up test environment with multiple packages
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "result-test-token");
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
auto package = MigrationManagerTestHelper::createLegacyPackageJson(
|
||||
"result-tool" + std::to_string(i), "1.0", "hash" + std::to_string(i), "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "result-tool" + std::to_string(i), package);
|
||||
}
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Perform migration
|
||||
bool result = manager.performMigration();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Check detailed results
|
||||
auto migrationResult = manager.getLastMigrationResult();
|
||||
ASSERT_TRUE(migrationResult.success);
|
||||
ASSERT_EQ(3, migrationResult.migratedPackages);
|
||||
ASSERT_EQ(3, migrationResult.totalPackages);
|
||||
ASSERT_TRUE(migrationResult.serverConfigMigrated);
|
||||
ASSERT_TRUE(migrationResult.packageDirectoryCreated);
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(MigrationWithExistingNewFormatData) {
|
||||
auto tempDir = MigrationManagerTestHelper::createTempDir();
|
||||
|
||||
// Create both legacy and new format data
|
||||
MigrationManagerTestHelper::createLegacyTokenFile(tempDir, "mixed-token");
|
||||
auto legacyPackage = MigrationManagerTestHelper::createLegacyPackageJson("legacy-mixed", "1.0", "hash", "x86_64");
|
||||
MigrationManagerTestHelper::createLegacyPackageFile(tempDir, "legacy-mixed", legacyPackage);
|
||||
|
||||
// Also create new format data
|
||||
MigrationManagerTestHelper::createNewFormatConfig(tempDir);
|
||||
MigrationManagerTestHelper::createPackagesDirectory(tempDir);
|
||||
|
||||
MigrationManager manager(tempDir);
|
||||
|
||||
// Should still need migration due to legacy data
|
||||
ASSERT_TRUE(manager.needsMigration());
|
||||
|
||||
// Migration should handle mixed environment
|
||||
bool result = manager.performMigration();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// Should preserve existing new format data and add legacy data
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "servers.json"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages"));
|
||||
ASSERT_TRUE(MigrationManagerTestHelper::fileExists(tempDir / "packages" / "legacy-mixed.json"));
|
||||
|
||||
MigrationManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
// Registration function
|
||||
void registerMigrationManagerTests() {
|
||||
register_MigrationManager_DefaultConstructor();
|
||||
register_MigrationManager_CustomConstructor();
|
||||
register_MigrationManager_NeedsMigrationNoLegacyData();
|
||||
register_MigrationManager_NeedsMigrationWithLegacyToken();
|
||||
register_MigrationManager_NeedsMigrationWithLegacyPackages();
|
||||
register_MigrationManager_NeedsMigrationWithNewFormat();
|
||||
register_MigrationManager_MigrateServerConfigurationSuccess();
|
||||
register_MigrationManager_MigratePackageMetadataSuccess();
|
||||
register_MigrationManager_CreatePackagesDirectorySuccess();
|
||||
register_MigrationManager_PerformFullMigrationSuccess();
|
||||
register_MigrationManager_PerformMigrationNoLegacyData();
|
||||
register_MigrationManager_ValidateMigrationSuccess();
|
||||
register_MigrationManager_ValidateMigrationFailure();
|
||||
register_MigrationManager_CreateBackupSuccess();
|
||||
register_MigrationManager_RollbackCapability();
|
||||
register_MigrationManager_PerformRollbackSuccess();
|
||||
register_MigrationManager_MigrationWithCorruptedLegacyData();
|
||||
register_MigrationManager_MigrationResultReporting();
|
||||
register_MigrationManager_MigrationWithExistingNewFormatData();
|
||||
}
|
545
getpkg/test/test_package_metadata.cpp
Normal file
545
getpkg/test/test_package_metadata.cpp
Normal file
@ -0,0 +1,545 @@
|
||||
#include "PackageMetadata.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// Test framework declarations from test_main.cpp
|
||||
class TestRunner {
|
||||
public:
|
||||
static TestRunner& instance();
|
||||
void addTest(const std::string& name, std::function<void()> test);
|
||||
};
|
||||
|
||||
#define ASSERT_TRUE(condition) \
|
||||
if (!(condition)) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition); \
|
||||
}
|
||||
|
||||
#define ASSERT_FALSE(condition) \
|
||||
if (condition) { \
|
||||
throw std::runtime_error("Assertion failed: " #condition " should be false"); \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected != actual"); \
|
||||
}
|
||||
|
||||
#define ASSERT_STR_EQ(expected, actual) \
|
||||
if ((expected) != (actual)) { \
|
||||
throw std::runtime_error("Assertion failed: expected '" + std::string(expected) + "' but got '" + std::string(actual) + "'"); \
|
||||
}
|
||||
|
||||
#define ASSERT_NOT_EMPTY(str) \
|
||||
if ((str).empty()) { \
|
||||
throw std::runtime_error("Assertion failed: string should not be empty"); \
|
||||
}
|
||||
|
||||
#define TEST(name) \
|
||||
void test_PackageMetadata_##name(); \
|
||||
void register_PackageMetadata_##name() { \
|
||||
TestRunner::instance().addTest("PackageMetadata::" #name, test_PackageMetadata_##name); \
|
||||
} \
|
||||
void test_PackageMetadata_##name()
|
||||
|
||||
// Test helper class for PackageMetadata testing
|
||||
class PackageMetadataTestHelper {
|
||||
public:
|
||||
static std::filesystem::path createTempDir() {
|
||||
auto tempDir = std::filesystem::temp_directory_path() / "getpkg_metadata_test" / std::to_string(std::time(nullptr));
|
||||
std::filesystem::create_directories(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
static void cleanupTempDir(const std::filesystem::path& dir) {
|
||||
if (std::filesystem::exists(dir)) {
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void createJsonFile(const std::filesystem::path& path, const nlohmann::json& content) {
|
||||
std::ofstream file(path);
|
||||
file << content.dump(2);
|
||||
file.close();
|
||||
}
|
||||
|
||||
static nlohmann::json readJsonFile(const std::filesystem::path& path) {
|
||||
std::ifstream file(path);
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
return j;
|
||||
}
|
||||
|
||||
static PackageMetadata createValidMetadata() {
|
||||
return PackageMetadata("test-tool", "2024.0115.1430", "abc123hash456", "x86_64", "getpkg.xyz", "2024-01-15T14:30:00Z");
|
||||
}
|
||||
|
||||
static nlohmann::json createLegacyJson() {
|
||||
return nlohmann::json{
|
||||
{"name", "legacy-tool"},
|
||||
{"version", "2023.1201.1000"},
|
||||
{"hash", "legacy123hash456"},
|
||||
{"arch", "x86_64"}
|
||||
// Note: no sourceServer or installDate fields
|
||||
};
|
||||
}
|
||||
|
||||
static nlohmann::json createCorruptedJson() {
|
||||
return nlohmann::json{
|
||||
{"name", ""}, // Invalid empty name
|
||||
{"version", "invalid-version-format"},
|
||||
{"hash", "short"}, // Too short hash
|
||||
{"arch", "invalid-arch"},
|
||||
{"sourceServer", "not-a-url"},
|
||||
{"installDate", "invalid-date-format"}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
TEST(DefaultConstructor) {
|
||||
PackageMetadata metadata;
|
||||
|
||||
ASSERT_TRUE(metadata.name.empty());
|
||||
ASSERT_TRUE(metadata.version.empty());
|
||||
ASSERT_TRUE(metadata.hash.empty());
|
||||
ASSERT_TRUE(metadata.arch.empty());
|
||||
ASSERT_TRUE(metadata.sourceServer.empty());
|
||||
ASSERT_TRUE(metadata.installDate.empty());
|
||||
|
||||
ASSERT_FALSE(metadata.isValid());
|
||||
}
|
||||
|
||||
TEST(ParameterizedConstructor) {
|
||||
PackageMetadata metadata("test-tool", "2024.0115.1430", "abc123hash456", "x86_64", "getpkg.xyz", "2024-01-15T14:30:00Z");
|
||||
|
||||
ASSERT_STR_EQ("test-tool", metadata.name);
|
||||
ASSERT_STR_EQ("2024.0115.1430", metadata.version);
|
||||
ASSERT_STR_EQ("abc123hash456", metadata.hash);
|
||||
ASSERT_STR_EQ("x86_64", metadata.arch);
|
||||
ASSERT_STR_EQ("getpkg.xyz", metadata.sourceServer);
|
||||
ASSERT_STR_EQ("2024-01-15T14:30:00Z", metadata.installDate);
|
||||
|
||||
ASSERT_TRUE(metadata.isValid());
|
||||
}
|
||||
|
||||
TEST(JsonSerialization) {
|
||||
PackageMetadata original = PackageMetadataTestHelper::createValidMetadata();
|
||||
|
||||
nlohmann::json j = original.toJson();
|
||||
|
||||
ASSERT_STR_EQ("test-tool", j["name"]);
|
||||
ASSERT_STR_EQ("2024.0115.1430", j["version"]);
|
||||
ASSERT_STR_EQ("abc123hash456", j["hash"]);
|
||||
ASSERT_STR_EQ("x86_64", j["arch"]);
|
||||
ASSERT_STR_EQ("getpkg.xyz", j["sourceServer"]);
|
||||
ASSERT_STR_EQ("2024-01-15T14:30:00Z", j["installDate"]);
|
||||
}
|
||||
|
||||
TEST(JsonDeserialization) {
|
||||
nlohmann::json j = {
|
||||
{"name", "deserialized-tool"},
|
||||
{"version", "2024.0116.0900"},
|
||||
{"hash", "def456hash789"},
|
||||
{"arch", "aarch64"},
|
||||
{"sourceServer", "packages.example.com"},
|
||||
{"installDate", "2024-01-16T09:00:00Z"}
|
||||
};
|
||||
|
||||
PackageMetadata metadata = PackageMetadata::fromJson(j);
|
||||
|
||||
ASSERT_STR_EQ("deserialized-tool", metadata.name);
|
||||
ASSERT_STR_EQ("2024.0116.0900", metadata.version);
|
||||
ASSERT_STR_EQ("def456hash789", metadata.hash);
|
||||
ASSERT_STR_EQ("aarch64", metadata.arch);
|
||||
ASSERT_STR_EQ("packages.example.com", metadata.sourceServer);
|
||||
ASSERT_STR_EQ("2024-01-16T09:00:00Z", metadata.installDate);
|
||||
|
||||
ASSERT_TRUE(metadata.isValid());
|
||||
}
|
||||
|
||||
TEST(LegacyJsonMigration) {
|
||||
nlohmann::json legacyJson = PackageMetadataTestHelper::createLegacyJson();
|
||||
|
||||
PackageMetadata metadata = PackageMetadata::fromLegacyJson(legacyJson, "getpkg.xyz");
|
||||
|
||||
ASSERT_STR_EQ("legacy-tool", metadata.name);
|
||||
ASSERT_STR_EQ("2023.1201.1000", metadata.version);
|
||||
ASSERT_STR_EQ("legacy123hash456", metadata.hash);
|
||||
ASSERT_STR_EQ("x86_64", metadata.arch);
|
||||
ASSERT_STR_EQ("getpkg.xyz", metadata.sourceServer);
|
||||
ASSERT_NOT_EMPTY(metadata.installDate); // Should be auto-generated
|
||||
|
||||
ASSERT_TRUE(metadata.isValid());
|
||||
}
|
||||
|
||||
TEST(LegacyJsonMigrationWithCustomServer) {
|
||||
nlohmann::json legacyJson = PackageMetadataTestHelper::createLegacyJson();
|
||||
|
||||
PackageMetadata metadata = PackageMetadata::fromLegacyJson(legacyJson, "custom.server.com");
|
||||
|
||||
ASSERT_STR_EQ("custom.server.com", metadata.sourceServer);
|
||||
ASSERT_TRUE(metadata.isValid());
|
||||
}
|
||||
|
||||
TEST(ValidationValid) {
|
||||
PackageMetadata metadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
|
||||
ASSERT_TRUE(metadata.isValid());
|
||||
ASSERT_TRUE(metadata.getValidationError().empty());
|
||||
}
|
||||
|
||||
TEST(ValidationInvalidName) {
|
||||
PackageMetadata metadata("", "2024.0115.1430", "abc123hash456", "x86_64", "getpkg.xyz");
|
||||
|
||||
ASSERT_FALSE(metadata.isValid());
|
||||
ASSERT_NOT_EMPTY(metadata.getValidationError());
|
||||
}
|
||||
|
||||
TEST(ValidationInvalidVersion) {
|
||||
PackageMetadata metadata("test-tool", "", "abc123hash456", "x86_64", "getpkg.xyz");
|
||||
|
||||
ASSERT_FALSE(metadata.isValid());
|
||||
ASSERT_NOT_EMPTY(metadata.getValidationError());
|
||||
}
|
||||
|
||||
TEST(ValidationInvalidHash) {
|
||||
PackageMetadata metadata("test-tool", "2024.0115.1430", "", "x86_64", "getpkg.xyz");
|
||||
|
||||
ASSERT_FALSE(metadata.isValid());
|
||||
ASSERT_NOT_EMPTY(metadata.getValidationError());
|
||||
}
|
||||
|
||||
TEST(ValidationInvalidArch) {
|
||||
PackageMetadata metadata("test-tool", "2024.0115.1430", "abc123hash456", "", "getpkg.xyz");
|
||||
|
||||
ASSERT_FALSE(metadata.isValid());
|
||||
ASSERT_NOT_EMPTY(metadata.getValidationError());
|
||||
}
|
||||
|
||||
TEST(ValidationInvalidServer) {
|
||||
PackageMetadata metadata("test-tool", "2024.0115.1430", "abc123hash456", "x86_64", "");
|
||||
|
||||
ASSERT_FALSE(metadata.isValid());
|
||||
ASSERT_NOT_EMPTY(metadata.getValidationError());
|
||||
}
|
||||
|
||||
TEST(FileOperationsSaveAndLoad) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
auto filePath = tempDir / "test-metadata.json";
|
||||
|
||||
PackageMetadata original = PackageMetadataTestHelper::createValidMetadata();
|
||||
|
||||
// Save to file
|
||||
bool saveResult = original.saveToFile(filePath);
|
||||
ASSERT_TRUE(saveResult);
|
||||
ASSERT_TRUE(std::filesystem::exists(filePath));
|
||||
|
||||
// Load from file
|
||||
PackageMetadata loaded = PackageMetadata::loadFromFile(filePath);
|
||||
|
||||
ASSERT_STR_EQ(original.name, loaded.name);
|
||||
ASSERT_STR_EQ(original.version, loaded.version);
|
||||
ASSERT_STR_EQ(original.hash, loaded.hash);
|
||||
ASSERT_STR_EQ(original.arch, loaded.arch);
|
||||
ASSERT_STR_EQ(original.sourceServer, loaded.sourceServer);
|
||||
ASSERT_STR_EQ(original.installDate, loaded.installDate);
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(FileOperationsInvalidPath) {
|
||||
PackageMetadata metadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
|
||||
// Try to save to invalid path
|
||||
bool saveResult = metadata.saveToFile("/invalid/path/metadata.json");
|
||||
ASSERT_FALSE(saveResult);
|
||||
|
||||
// Try to load from non-existent file
|
||||
PackageMetadata loaded = PackageMetadata::loadFromFile("/nonexistent/file.json");
|
||||
ASSERT_FALSE(loaded.isValid());
|
||||
}
|
||||
|
||||
TEST(NeedsUpdateComparison) {
|
||||
PackageMetadata metadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
|
||||
// Same hash - no update needed
|
||||
ASSERT_FALSE(metadata.needsUpdate("abc123hash456"));
|
||||
|
||||
// Different hash - update needed
|
||||
ASSERT_TRUE(metadata.needsUpdate("different123hash456"));
|
||||
|
||||
// Empty hash - update needed
|
||||
ASSERT_TRUE(metadata.needsUpdate(""));
|
||||
}
|
||||
|
||||
TEST(TimestampGeneration) {
|
||||
PackageMetadata metadata;
|
||||
|
||||
std::string timestamp = metadata.getCurrentTimestamp();
|
||||
ASSERT_NOT_EMPTY(timestamp);
|
||||
|
||||
// Should be in ISO format (basic check)
|
||||
ASSERT_TRUE(timestamp.find("T") != std::string::npos);
|
||||
ASSERT_TRUE(timestamp.find("Z") != std::string::npos);
|
||||
}
|
||||
|
||||
// PackageMetadataManager Tests
|
||||
|
||||
TEST(ManagerDefaultConstructor) {
|
||||
PackageMetadataManager manager;
|
||||
|
||||
// Should initialize with default config directory
|
||||
auto packagesDir = manager.getPackagesDirectory();
|
||||
ASSERT_FALSE(packagesDir.empty());
|
||||
}
|
||||
|
||||
TEST(ManagerCustomConstructor) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
|
||||
auto packagesDir = manager.getPackagesDirectory();
|
||||
ASSERT_STR_EQ((tempDir / "packages").string(), packagesDir.string());
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerEnsurePackagesDirectory) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
|
||||
bool result = manager.ensurePackagesDirectory();
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
auto packagesDir = manager.getPackagesDirectory();
|
||||
ASSERT_TRUE(std::filesystem::exists(packagesDir));
|
||||
ASSERT_TRUE(std::filesystem::is_directory(packagesDir));
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerSaveAndLoadPackageMetadata) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
manager.ensurePackagesDirectory();
|
||||
|
||||
PackageMetadata metadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
|
||||
// Save metadata
|
||||
bool saveResult = manager.savePackageMetadata(metadata);
|
||||
ASSERT_TRUE(saveResult);
|
||||
|
||||
// Check if package exists
|
||||
ASSERT_TRUE(manager.packageExists("test-tool"));
|
||||
|
||||
// Load metadata
|
||||
PackageMetadata loaded = manager.loadPackageMetadata("test-tool");
|
||||
ASSERT_TRUE(loaded.isValid());
|
||||
ASSERT_STR_EQ(metadata.name, loaded.name);
|
||||
ASSERT_STR_EQ(metadata.version, loaded.version);
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerRemovePackageMetadata) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
manager.ensurePackagesDirectory();
|
||||
|
||||
PackageMetadata metadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
manager.savePackageMetadata(metadata);
|
||||
|
||||
ASSERT_TRUE(manager.packageExists("test-tool"));
|
||||
|
||||
bool removeResult = manager.removePackageMetadata("test-tool");
|
||||
ASSERT_TRUE(removeResult);
|
||||
|
||||
ASSERT_FALSE(manager.packageExists("test-tool"));
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerListInstalledPackages) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
manager.ensurePackagesDirectory();
|
||||
|
||||
// Save multiple packages
|
||||
PackageMetadata metadata1("tool1", "1.0", "hash1", "x86_64", "server1");
|
||||
PackageMetadata metadata2("tool2", "2.0", "hash2", "aarch64", "server2");
|
||||
|
||||
manager.savePackageMetadata(metadata1);
|
||||
manager.savePackageMetadata(metadata2);
|
||||
|
||||
auto packages = manager.listInstalledPackages();
|
||||
ASSERT_EQ(2, packages.size());
|
||||
|
||||
// Should contain both tools (order may vary)
|
||||
bool foundTool1 = std::find(packages.begin(), packages.end(), "tool1") != packages.end();
|
||||
bool foundTool2 = std::find(packages.begin(), packages.end(), "tool2") != packages.end();
|
||||
|
||||
ASSERT_TRUE(foundTool1);
|
||||
ASSERT_TRUE(foundTool2);
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerGetAllPackageMetadata) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
manager.ensurePackagesDirectory();
|
||||
|
||||
// Save multiple packages
|
||||
PackageMetadata metadata1("tool1", "1.0", "hash1", "x86_64", "server1");
|
||||
PackageMetadata metadata2("tool2", "2.0", "hash2", "aarch64", "server2");
|
||||
|
||||
manager.savePackageMetadata(metadata1);
|
||||
manager.savePackageMetadata(metadata2);
|
||||
|
||||
auto allMetadata = manager.getAllPackageMetadata();
|
||||
ASSERT_EQ(2, allMetadata.size());
|
||||
|
||||
// Verify metadata content
|
||||
bool foundTool1 = false, foundTool2 = false;
|
||||
for (const auto& meta : allMetadata) {
|
||||
if (meta.name == "tool1") {
|
||||
foundTool1 = true;
|
||||
ASSERT_STR_EQ("1.0", meta.version);
|
||||
} else if (meta.name == "tool2") {
|
||||
foundTool2 = true;
|
||||
ASSERT_STR_EQ("2.0", meta.version);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(foundTool1);
|
||||
ASSERT_TRUE(foundTool2);
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerMigrationFromLegacyFormat) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
// Create legacy package files in root config directory
|
||||
auto legacyFile1 = tempDir / "legacy-tool1.json";
|
||||
auto legacyFile2 = tempDir / "legacy-tool2.json";
|
||||
|
||||
PackageMetadataTestHelper::createJsonFile(legacyFile1, PackageMetadataTestHelper::createLegacyJson());
|
||||
PackageMetadataTestHelper::createJsonFile(legacyFile2, nlohmann::json{
|
||||
{"name", "legacy-tool2"},
|
||||
{"version", "2023.1202.1100"},
|
||||
{"hash", "legacy789hash012"},
|
||||
{"arch", "aarch64"}
|
||||
});
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
|
||||
// Find legacy files
|
||||
auto legacyFiles = manager.findLegacyPackageFiles();
|
||||
ASSERT_EQ(2, legacyFiles.size());
|
||||
|
||||
// Perform migration
|
||||
bool migrationResult = manager.migrateFromLegacyFormat();
|
||||
ASSERT_TRUE(migrationResult);
|
||||
|
||||
// Verify packages directory was created
|
||||
ASSERT_TRUE(std::filesystem::exists(manager.getPackagesDirectory()));
|
||||
|
||||
// Verify packages were migrated
|
||||
ASSERT_TRUE(manager.packageExists("legacy-tool1"));
|
||||
ASSERT_TRUE(manager.packageExists("legacy-tool2"));
|
||||
|
||||
// Verify metadata has server information
|
||||
PackageMetadata migrated1 = manager.loadPackageMetadata("legacy-tool1");
|
||||
ASSERT_STR_EQ("getpkg.xyz", migrated1.sourceServer);
|
||||
ASSERT_NOT_EMPTY(migrated1.installDate);
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerValidateAllPackageMetadata) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
manager.ensurePackagesDirectory();
|
||||
|
||||
// Save valid and invalid metadata
|
||||
PackageMetadata validMetadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
PackageMetadata invalidMetadata("", "", "", "", ""); // All empty - invalid
|
||||
|
||||
manager.savePackageMetadata(validMetadata);
|
||||
|
||||
// Manually create invalid metadata file
|
||||
auto invalidFile = manager.getPackagesDirectory() / "invalid-tool.json";
|
||||
PackageMetadataTestHelper::createJsonFile(invalidFile, PackageMetadataTestHelper::createCorruptedJson());
|
||||
|
||||
bool validationResult = manager.validateAllPackageMetadata();
|
||||
ASSERT_FALSE(validationResult); // Should fail due to invalid metadata
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ManagerCleanupInvalidMetadata) {
|
||||
auto tempDir = PackageMetadataTestHelper::createTempDir();
|
||||
|
||||
PackageMetadataManager manager(tempDir);
|
||||
manager.ensurePackagesDirectory();
|
||||
|
||||
// Save valid metadata
|
||||
PackageMetadata validMetadata = PackageMetadataTestHelper::createValidMetadata();
|
||||
manager.savePackageMetadata(validMetadata);
|
||||
|
||||
// Create invalid metadata file
|
||||
auto invalidFile = manager.getPackagesDirectory() / "invalid-tool.json";
|
||||
PackageMetadataTestHelper::createJsonFile(invalidFile, PackageMetadataTestHelper::createCorruptedJson());
|
||||
|
||||
// Should have 2 files initially
|
||||
auto packagesBefore = manager.listInstalledPackages();
|
||||
ASSERT_EQ(2, packagesBefore.size());
|
||||
|
||||
// Cleanup invalid metadata
|
||||
int cleanedCount = manager.cleanupInvalidMetadata();
|
||||
ASSERT_EQ(1, cleanedCount);
|
||||
|
||||
// Should have 1 file after cleanup
|
||||
auto packagesAfter = manager.listInstalledPackages();
|
||||
ASSERT_EQ(1, packagesAfter.size());
|
||||
ASSERT_STR_EQ("test-tool", packagesAfter[0]);
|
||||
|
||||
PackageMetadataTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
// Registration function
|
||||
void registerPackageMetadataTests() {
|
||||
register_PackageMetadata_DefaultConstructor();
|
||||
register_PackageMetadata_ParameterizedConstructor();
|
||||
register_PackageMetadata_JsonSerialization();
|
||||
register_PackageMetadata_JsonDeserialization();
|
||||
register_PackageMetadata_LegacyJsonMigration();
|
||||
register_PackageMetadata_LegacyJsonMigrationWithCustomServer();
|
||||
register_PackageMetadata_ValidationValid();
|
||||
register_PackageMetadata_ValidationInvalidName();
|
||||
register_PackageMetadata_ValidationInvalidVersion();
|
||||
register_PackageMetadata_ValidationInvalidHash();
|
||||
register_PackageMetadata_ValidationInvalidArch();
|
||||
register_PackageMetadata_ValidationInvalidServer();
|
||||
register_PackageMetadata_FileOperationsSaveAndLoad();
|
||||
register_PackageMetadata_FileOperationsInvalidPath();
|
||||
register_PackageMetadata_NeedsUpdateComparison();
|
||||
register_PackageMetadata_TimestampGeneration();
|
||||
register_PackageMetadata_ManagerDefaultConstructor();
|
||||
register_PackageMetadata_ManagerCustomConstructor();
|
||||
register_PackageMetadata_ManagerEnsurePackagesDirectory();
|
||||
register_PackageMetadata_ManagerSaveAndLoadPackageMetadata();
|
||||
register_PackageMetadata_ManagerRemovePackageMetadata();
|
||||
register_PackageMetadata_ManagerListInstalledPackages();
|
||||
register_PackageMetadata_ManagerGetAllPackageMetadata();
|
||||
register_PackageMetadata_ManagerMigrationFromLegacyFormat();
|
||||
register_PackageMetadata_ManagerValidateAllPackageMetadata();
|
||||
register_PackageMetadata_ManagerCleanupInvalidMetadata();
|
||||
}
|
306
getpkg/test/test_server_manager.cpp
Normal file
306
getpkg/test/test_server_manager.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
#include "test_framework.hpp"
|
||||
#include "ServerManager.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
// Test helper class for ServerManager testing
|
||||
class ServerManagerTestHelper {
|
||||
public:
|
||||
static std::filesystem::path createTempConfigDir() {
|
||||
auto tempDir = std::filesystem::temp_directory_path() / "getpkg_test" / std::to_string(std::time(nullptr));
|
||||
std::filesystem::create_directories(tempDir);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
static void cleanupTempDir(const std::filesystem::path& dir) {
|
||||
if (std::filesystem::exists(dir)) {
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
}
|
||||
|
||||
static void createLegacyTokenFile(const std::filesystem::path& configDir, const std::string& token) {
|
||||
auto legacyDir = configDir / "getpkg.xyz";
|
||||
std::filesystem::create_directories(legacyDir);
|
||||
|
||||
std::ofstream tokenFile(legacyDir / "write_token.txt");
|
||||
tokenFile << token;
|
||||
tokenFile.close();
|
||||
}
|
||||
|
||||
static void createCorruptedConfigFile(const std::filesystem::path& configDir) {
|
||||
std::ofstream configFile(configDir / "servers.json");
|
||||
configFile << "{ invalid json content";
|
||||
configFile.close();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ServerManager, DefaultConfiguration) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
|
||||
// Set environment variable to use temp directory
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(1, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
ASSERT_STR_EQ("getpkg.xyz", manager.getDefaultServer());
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ServerManager, AddValidServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
|
||||
auto result = manager.addServer("packages.example.com");
|
||||
ASSERT_EQ(ServerManagerError::None, result);
|
||||
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(2, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
ASSERT_STR_EQ("packages.example.com", servers[1]);
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ServerManager, AddInvalidServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
|
||||
// Test invalid URLs
|
||||
auto result1 = manager.addServer("not-a-url");
|
||||
ASSERT_EQ(ServerManagerError::InvalidUrl, result1);
|
||||
|
||||
auto result2 = manager.addServer("");
|
||||
ASSERT_EQ(ServerManagerError::InvalidUrl, result2);
|
||||
|
||||
auto result3 = manager.addServer("ftp://invalid-protocol.com");
|
||||
ASSERT_EQ(ServerManagerError::InvalidUrl, result3);
|
||||
|
||||
// Should still have only default server
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(1, servers.size());
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ServerManager, AddDuplicateServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
|
||||
auto result1 = manager.addServer("packages.example.com");
|
||||
ASSERT_EQ(ServerManagerError::None, result1);
|
||||
|
||||
auto result2 = manager.addServer("packages.example.com");
|
||||
ASSERT_EQ(ServerManagerError::ServerAlreadyExists, result2);
|
||||
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(2, servers.size()); // Should not add duplicate
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(RemoveServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
manager.addServer("packages.example.com");
|
||||
manager.addServer("test.server.com");
|
||||
|
||||
auto result = manager.removeServer("packages.example.com");
|
||||
ASSERT_EQ(ServerManagerError::None, result);
|
||||
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(2, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
ASSERT_STR_EQ("test.server.com", servers[1]);
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(RemoveNonExistentServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
|
||||
auto result = manager.removeServer("nonexistent.server.com");
|
||||
ASSERT_EQ(ServerManagerError::ServerNotFound, result);
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(RemoveLastServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
|
||||
auto result = manager.removeServer("getpkg.xyz");
|
||||
ASSERT_EQ(ServerManagerError::LastServerRemoval, result);
|
||||
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(1, servers.size()); // Should still have the server
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(WriteTokenManagement) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
manager.addServer("packages.example.com");
|
||||
|
||||
// Set write token
|
||||
auto result = manager.setWriteToken("packages.example.com", "test-token-123");
|
||||
ASSERT_EQ(ServerManagerError::None, result);
|
||||
|
||||
// Verify token
|
||||
ASSERT_TRUE(manager.hasWriteToken("packages.example.com"));
|
||||
ASSERT_STR_EQ("test-token-123", manager.getWriteToken("packages.example.com"));
|
||||
|
||||
// Test servers with tokens
|
||||
auto serversWithTokens = manager.getServersWithTokens();
|
||||
ASSERT_EQ(1, serversWithTokens.size());
|
||||
ASSERT_STR_EQ("packages.example.com", serversWithTokens[0]);
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(DefaultPublishServer) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
ServerManager manager;
|
||||
manager.ensureDefaultConfiguration();
|
||||
manager.addServer("packages.example.com");
|
||||
manager.addServer("test.server.com");
|
||||
|
||||
// No tokens initially
|
||||
ASSERT_STR_EQ("", manager.getDefaultPublishServer());
|
||||
|
||||
// Add token to second server
|
||||
manager.setWriteToken("test.server.com", "token2");
|
||||
ASSERT_STR_EQ("test.server.com", manager.getDefaultPublishServer());
|
||||
|
||||
// Add token to first server - should become default
|
||||
manager.setWriteToken("getpkg.xyz", "token1");
|
||||
ASSERT_STR_EQ("getpkg.xyz", manager.getDefaultPublishServer());
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ConfigurationPersistence) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
{
|
||||
ServerManager manager1;
|
||||
manager1.ensureDefaultConfiguration();
|
||||
manager1.addServer("packages.example.com");
|
||||
manager1.setWriteToken("packages.example.com", "test-token");
|
||||
manager1.saveConfiguration();
|
||||
}
|
||||
|
||||
{
|
||||
ServerManager manager2;
|
||||
auto result = manager2.loadConfiguration();
|
||||
ASSERT_EQ(ServerManagerError::None, result);
|
||||
|
||||
auto servers = manager2.getServers();
|
||||
ASSERT_EQ(2, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
ASSERT_STR_EQ("packages.example.com", servers[1]);
|
||||
|
||||
ASSERT_TRUE(manager2.hasWriteToken("packages.example.com"));
|
||||
ASSERT_STR_EQ("test-token", manager2.getWriteToken("packages.example.com"));
|
||||
}
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(CorruptedConfigurationRecovery) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
// Create corrupted config file
|
||||
ServerManagerTestHelper::createCorruptedConfigFile(tempDir / ".config" / "getpkg");
|
||||
|
||||
ServerManager manager;
|
||||
auto result = manager.loadConfiguration();
|
||||
|
||||
// Should recover by creating default configuration
|
||||
ASSERT_EQ(ServerManagerError::None, result);
|
||||
|
||||
auto servers = manager.getServers();
|
||||
ASSERT_EQ(1, servers.size());
|
||||
ASSERT_STR_EQ("getpkg.xyz", servers[0]);
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(LegacyMigration) {
|
||||
auto tempDir = ServerManagerTestHelper::createTempConfigDir();
|
||||
setenv("HOME", tempDir.c_str(), 1);
|
||||
|
||||
auto configDir = tempDir / ".config" / "getpkg";
|
||||
std::filesystem::create_directories(configDir);
|
||||
|
||||
// Create legacy token file
|
||||
ServerManagerTestHelper::createLegacyTokenFile(configDir, "legacy-token-123");
|
||||
|
||||
ServerManager manager;
|
||||
bool migrated = manager.migrateFromLegacy();
|
||||
ASSERT_TRUE(migrated);
|
||||
|
||||
// Verify migration
|
||||
ASSERT_TRUE(manager.hasWriteToken("getpkg.xyz"));
|
||||
ASSERT_STR_EQ("legacy-token-123", manager.getWriteToken("getpkg.xyz"));
|
||||
|
||||
ServerManagerTestHelper::cleanupTempDir(tempDir);
|
||||
}
|
||||
|
||||
TEST(ErrorMessages) {
|
||||
ServerManager manager;
|
||||
|
||||
ASSERT_NOT_EMPTY(manager.getErrorMessage(ServerManagerError::InvalidUrl));
|
||||
ASSERT_NOT_EMPTY(manager.getErrorMessage(ServerManagerError::ServerNotFound));
|
||||
ASSERT_NOT_EMPTY(manager.getErrorMessage(ServerManagerError::ServerAlreadyExists));
|
||||
ASSERT_NOT_EMPTY(manager.getErrorMessage(ServerManagerError::LastServerRemoval));
|
||||
}
|
||||
|
||||
// Registration function
|
||||
void registerServerManagerTests() {
|
||||
register_ServerManager_DefaultConfiguration();
|
||||
register_ServerManager_AddValidServer();
|
||||
register_ServerManager_AddInvalidServer();
|
||||
register_ServerManager_AddDuplicateServer();
|
||||
register_ServerManager_RemoveServer();
|
||||
register_ServerManager_RemoveNonExistentServer();
|
||||
register_ServerManager_RemoveLastServer();
|
||||
register_ServerManager_WriteTokenManagement();
|
||||
register_ServerManager_DefaultPublishServer();
|
||||
register_ServerManager_ConfigurationPersistence();
|
||||
register_ServerManager_CorruptedConfigurationRecovery();
|
||||
register_ServerManager_LegacyMigration();
|
||||
register_ServerManager_ErrorMessages();
|
||||
}
|
Reference in New Issue
Block a user