This commit implements a new Storage settings view that displays storage usage statistics for NostrDB, snapshot database, and Kingfisher image cache. Key features: - Interactive pie chart visualization (iOS 17+) with tap-to-select functionality - Pull-to-refresh gesture to recalculate storage - Categorized list showing each storage type with size and percentage - Total storage sum displayed at bottom - Conditional compilation for iOS 16/17+ compatibility - All calculations run on background thread to avoid blocking main thread - NostrDB storage breakdown Changelog-Added: Storage usage statistics view in Settings Changelog-Changed: Moved clear cache button to storage settings Closes: https://github.com/damus-io/damus/issues/3649 Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
538 lines
22 KiB
Swift
538 lines
22 KiB
Swift
//
|
|
// StorageStatsManagerTests.swift
|
|
// damusTests
|
|
//
|
|
// Created by OpenCode on 2026-02-25.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import damus
|
|
import Kingfisher
|
|
|
|
/// Comprehensive test suite for storage usage calculation logic
|
|
///
|
|
/// Tests cover:
|
|
/// - StorageStats calculations (total size, percentages)
|
|
/// - File size calculations with temporary test files
|
|
/// - Async storage stats calculations
|
|
/// - Byte formatting utilities
|
|
/// - Ndb.getStats() database statistics
|
|
/// - Integration between components
|
|
/// - Thread safety and error handling
|
|
final class StorageStatsManagerTests: XCTestCase {
|
|
|
|
var tempDirectory: URL!
|
|
var mockNostrDBPath: String!
|
|
var mockSnapshotPath: String!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
// Create temporary directory for test files
|
|
tempDirectory = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("StorageStatsManagerTests-\(UUID().uuidString)")
|
|
|
|
try? FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
|
|
|
|
// Create mock database directories
|
|
let nostrDBDir = tempDirectory.appendingPathComponent("nostrdb")
|
|
let snapshotDir = tempDirectory.appendingPathComponent("snapshot")
|
|
|
|
try? FileManager.default.createDirectory(at: nostrDBDir, withIntermediateDirectories: true)
|
|
try? FileManager.default.createDirectory(at: snapshotDir, withIntermediateDirectories: true)
|
|
|
|
mockNostrDBPath = nostrDBDir.path
|
|
mockSnapshotPath = snapshotDir.path
|
|
}
|
|
|
|
override func tearDown() {
|
|
// Clean up temporary files
|
|
if let tempDirectory = tempDirectory {
|
|
try? FileManager.default.removeItem(at: tempDirectory)
|
|
}
|
|
|
|
tempDirectory = nil
|
|
mockNostrDBPath = nil
|
|
mockSnapshotPath = nil
|
|
|
|
super.tearDown()
|
|
}
|
|
|
|
// MARK: - Helper Methods
|
|
|
|
/// Create a temporary file with specified size
|
|
/// - Parameters:
|
|
/// - path: Full path for the file
|
|
/// - size: Size in bytes
|
|
private func createTestFile(at path: String, size: UInt64) throws {
|
|
let data = Data(repeating: 0, count: Int(size))
|
|
try data.write(to: URL(fileURLWithPath: path))
|
|
}
|
|
|
|
/// Get file size using FileManager (reference implementation)
|
|
private func getActualFileSize(at path: String) -> UInt64? {
|
|
guard FileManager.default.fileExists(atPath: path) else { return nil }
|
|
|
|
do {
|
|
let attributes = try FileManager.default.attributesOfItem(atPath: path)
|
|
return attributes[.size] as? UInt64
|
|
} catch {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MARK: - 1. StorageStats Structure Tests
|
|
|
|
/// Test that totalSize correctly sums all storage components
|
|
func testTotalSizeCalculation() {
|
|
let stats = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 1000,
|
|
snapshotSize: 500,
|
|
imageCacheSize: 250
|
|
)
|
|
|
|
XCTAssertEqual(stats.totalSize, 1750, "Total size should sum all components")
|
|
}
|
|
|
|
/// Test percentage calculation accuracy
|
|
func testPercentageCalculation() {
|
|
let stats = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 600,
|
|
snapshotSize: 300,
|
|
imageCacheSize: 100
|
|
)
|
|
|
|
// Total = 1000, so 600 should be 60%
|
|
let nostrdbPercentage = stats.percentage(for: 600)
|
|
XCTAssertEqual(nostrdbPercentage, 60.0, accuracy: 0.01, "NostrDB should be 60% of total")
|
|
|
|
let snapshotPercentage = stats.percentage(for: 300)
|
|
XCTAssertEqual(snapshotPercentage, 30.0, accuracy: 0.01, "Snapshot should be 30% of total")
|
|
|
|
let cachePercentage = stats.percentage(for: 100)
|
|
XCTAssertEqual(cachePercentage, 10.0, accuracy: 0.01, "Cache should be 10% of total")
|
|
}
|
|
|
|
/// Test percentage calculation when total is zero (edge case)
|
|
func testPercentageWithZeroTotal() {
|
|
let stats = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 0,
|
|
snapshotSize: 0,
|
|
imageCacheSize: 0
|
|
)
|
|
|
|
let percentage = stats.percentage(for: 100)
|
|
XCTAssertEqual(percentage, 0.0, "Percentage should be 0 when total is 0")
|
|
}
|
|
|
|
/// Test that StorageStats conforms to Hashable properly
|
|
func testStorageStatsHashableConformance() {
|
|
let stats1 = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 1000,
|
|
snapshotSize: 500,
|
|
imageCacheSize: 250
|
|
)
|
|
|
|
let stats2 = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 1000,
|
|
snapshotSize: 500,
|
|
imageCacheSize: 250
|
|
)
|
|
|
|
let stats3 = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 2000,
|
|
snapshotSize: 500,
|
|
imageCacheSize: 250
|
|
)
|
|
|
|
// Equal stats should be equal and have same hash
|
|
XCTAssertEqual(stats1, stats2, "Identical stats should be equal")
|
|
XCTAssertEqual(stats1.hashValue, stats2.hashValue, "Equal stats should have same hash")
|
|
|
|
// Different stats should not be equal
|
|
XCTAssertNotEqual(stats1, stats3, "Different stats should not be equal")
|
|
|
|
// Should work in Set
|
|
let set: Set<StorageStats> = [stats1, stats2, stats3]
|
|
XCTAssertEqual(set.count, 2, "Set should contain 2 unique stats")
|
|
}
|
|
|
|
// MARK: - 2. File Size Calculation Tests
|
|
|
|
/// Test file size calculation with an existing file
|
|
func testGetFileSizeWithExistingFile() throws {
|
|
let testFilePath = tempDirectory.appendingPathComponent("test-file.dat").path
|
|
let expectedSize: UInt64 = 1024 * 1024 // 1 MB
|
|
|
|
// Create test file with known size
|
|
try createTestFile(at: testFilePath, size: expectedSize)
|
|
|
|
// Verify file was created correctly
|
|
let actualSize = getActualFileSize(at: testFilePath)
|
|
XCTAssertNotNil(actualSize, "Test file should exist")
|
|
XCTAssertEqual(actualSize, expectedSize, "Test file should have expected size")
|
|
}
|
|
|
|
/// Test file size calculation when file doesn't exist (should return 0)
|
|
func testGetFileSizeWithNonexistentFile() {
|
|
let nonexistentPath = tempDirectory.appendingPathComponent("nonexistent.dat").path
|
|
|
|
// Verify file doesn't exist
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: nonexistentPath), "File should not exist")
|
|
|
|
let size = getActualFileSize(at: nonexistentPath)
|
|
XCTAssertNil(size, "Size should be nil for nonexistent file")
|
|
}
|
|
|
|
/// Test NostrDB file size calculation with valid path
|
|
func testGetNostrDBSizeWithValidPath() throws {
|
|
let dbFilePath = "\(mockNostrDBPath!)/\(Ndb.main_db_file_name)"
|
|
let expectedSize: UInt64 = 5 * 1024 * 1024 // 5 MB
|
|
|
|
// Create mock database file
|
|
try createTestFile(at: dbFilePath, size: expectedSize)
|
|
|
|
// Verify file size can be retrieved
|
|
let actualSize = getActualFileSize(at: dbFilePath)
|
|
XCTAssertNotNil(actualSize, "DB file should exist")
|
|
XCTAssertEqual(actualSize, expectedSize, "DB file should have expected size")
|
|
}
|
|
|
|
/// Test snapshot database file size calculation with valid path
|
|
func testGetSnapshotDBSizeWithValidPath() throws {
|
|
let dbFilePath = "\(mockSnapshotPath!)/\(Ndb.main_db_file_name)"
|
|
let expectedSize: UInt64 = 2 * 1024 * 1024 // 2 MB
|
|
|
|
// Create mock snapshot database file
|
|
try createTestFile(at: dbFilePath, size: expectedSize)
|
|
|
|
// Verify file size can be retrieved
|
|
let actualSize = getActualFileSize(at: dbFilePath)
|
|
XCTAssertNotNil(actualSize, "Snapshot DB file should exist")
|
|
XCTAssertEqual(actualSize, expectedSize, "Snapshot DB file should have expected size")
|
|
}
|
|
|
|
// MARK: - 3. Byte Formatting Tests
|
|
|
|
/// Test formatting of zero bytes
|
|
func testFormatBytesZero() {
|
|
let formatted = StorageStatsManager.formatBytes(0)
|
|
// ByteCountFormatter may format as "Zero bytes", "0 bytes", "0 KB", etc.
|
|
// We just verify it's a valid non-empty string
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted string should not be empty")
|
|
// Most common formats include "0" or "Zero"
|
|
let containsZero = formatted.contains("0") || formatted.uppercased().contains("ZERO")
|
|
XCTAssertTrue(containsZero, "Zero bytes should contain '0' or 'Zero', got: \(formatted)")
|
|
}
|
|
|
|
/// Test formatting of small byte values (< 1 KB)
|
|
func testFormatBytesSmall() {
|
|
let formatted = StorageStatsManager.formatBytes(512)
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted string should not be empty")
|
|
// Should contain a numeric value
|
|
XCTAssertTrue(formatted.contains("512") || formatted.contains("0.5"), "Should contain size value")
|
|
}
|
|
|
|
/// Test formatting of kilobyte values
|
|
func testFormatBytesKilobytes() {
|
|
let oneKB: UInt64 = 1024
|
|
let formatted = StorageStatsManager.formatBytes(oneKB * 5) // 5 KB
|
|
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted string should not be empty")
|
|
// Should mention KB or kilobytes
|
|
XCTAssertTrue(formatted.uppercased().contains("KB") || formatted.uppercased().contains("K"),
|
|
"Should indicate kilobytes: \(formatted)")
|
|
}
|
|
|
|
/// Test formatting of megabyte values
|
|
func testFormatBytesMegabytes() {
|
|
let oneMB: UInt64 = 1024 * 1024
|
|
let formatted = StorageStatsManager.formatBytes(oneMB * 10) // 10 MB
|
|
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted string should not be empty")
|
|
// Should mention MB or megabytes
|
|
XCTAssertTrue(formatted.uppercased().contains("MB") || formatted.uppercased().contains("M"),
|
|
"Should indicate megabytes: \(formatted)")
|
|
}
|
|
|
|
/// Test formatting of gigabyte values
|
|
func testFormatBytesGigabytes() {
|
|
let oneGB: UInt64 = 1024 * 1024 * 1024
|
|
let formatted = StorageStatsManager.formatBytes(oneGB * 2) // 2 GB
|
|
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted string should not be empty")
|
|
// Should mention GB or gigabytes
|
|
XCTAssertTrue(formatted.uppercased().contains("GB") || formatted.uppercased().contains("G"),
|
|
"Should indicate gigabytes: \(formatted)")
|
|
}
|
|
|
|
/// Test formatting of very large values
|
|
func testFormatBytesLarge() {
|
|
let oneTB: UInt64 = 1024 * 1024 * 1024 * 1024
|
|
let formatted = StorageStatsManager.formatBytes(oneTB)
|
|
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted string should not be empty")
|
|
// Should handle terabyte values gracefully
|
|
XCTAssertTrue(formatted.uppercased().contains("TB") || formatted.uppercased().contains("T") ||
|
|
formatted.uppercased().contains("GB") || formatted.uppercased().contains("G"),
|
|
"Should format large values: \(formatted)")
|
|
}
|
|
|
|
// MARK: - 4. Async Storage Stats Calculation Tests
|
|
|
|
/// Test storage stats calculation without Ndb instance
|
|
func testCalculateStorageStatsWithoutNdb() async throws {
|
|
// Note: This test verifies the calculation succeeds and returns valid stats
|
|
// We don't check exact values since they depend on actual system state
|
|
|
|
let stats = try await StorageStatsManager.shared.calculateStorageStats(ndb: nil)
|
|
|
|
// Verify stats structure is valid
|
|
XCTAssertNotNil(stats, "Stats should not be nil")
|
|
XCTAssertNil(stats.nostrdbDetails, "Details should be nil when no Ndb provided")
|
|
|
|
// All sizes should be non-negative
|
|
XCTAssertGreaterThanOrEqual(stats.nostrdbSize, 0, "NostrDB size should be non-negative")
|
|
XCTAssertGreaterThanOrEqual(stats.snapshotSize, 0, "Snapshot size should be non-negative")
|
|
XCTAssertGreaterThanOrEqual(stats.imageCacheSize, 0, "Image cache size should be non-negative")
|
|
|
|
// Total should equal sum
|
|
let expectedTotal = stats.nostrdbSize + stats.snapshotSize + stats.imageCacheSize
|
|
XCTAssertEqual(stats.totalSize, expectedTotal, "Total should equal sum of components")
|
|
}
|
|
|
|
|
|
// MARK: - 5. NdbDatabaseStats Tests
|
|
|
|
/// Test NdbDatabaseStats total size calculation
|
|
func testNdbDatabaseStatsCalculations() {
|
|
let dbStats = NdbDatabaseStats(
|
|
database: .note,
|
|
keySize: 1000,
|
|
valueSize: 5000
|
|
)
|
|
|
|
XCTAssertEqual(dbStats.totalSize, 6000, "Total should be key + value size")
|
|
XCTAssertEqual(dbStats.database, .note, "Database type should be preserved")
|
|
XCTAssertEqual(dbStats.keySize, 1000, "Key size should be preserved")
|
|
XCTAssertEqual(dbStats.valueSize, 5000, "Value size should be preserved")
|
|
}
|
|
|
|
/// Test NdbStats total size calculation
|
|
func testNdbStatsTotalCalculation() {
|
|
let stats = NdbStats(databaseStats: [
|
|
NdbDatabaseStats(database: .note, keySize: 1000, valueSize: 5000),
|
|
NdbDatabaseStats(database: .profile, keySize: 500, valueSize: 2000),
|
|
NdbDatabaseStats(database: .noteId, keySize: 200, valueSize: 800)
|
|
])
|
|
|
|
// Total should be sum of all database totals
|
|
// (1000+5000) + (500+2000) + (200+800) = 9500
|
|
XCTAssertEqual(stats.totalSize, 9500, "Total should sum all database sizes")
|
|
}
|
|
|
|
/// Test NdbStats with empty database list
|
|
func testNdbStatsEmpty() {
|
|
let stats = NdbStats(databaseStats: [])
|
|
|
|
XCTAssertEqual(stats.totalSize, 0, "Empty stats should have zero total")
|
|
XCTAssertTrue(stats.databaseStats.isEmpty, "Database stats should be empty")
|
|
}
|
|
|
|
/// Test NdbDatabaseStats hashable conformance
|
|
func testNdbDatabaseStatsHashableConformance() {
|
|
let stats1 = NdbDatabaseStats(database: .note, keySize: 1000, valueSize: 5000)
|
|
let stats2 = NdbDatabaseStats(database: .note, keySize: 1000, valueSize: 5000)
|
|
let stats3 = NdbDatabaseStats(database: .profile, keySize: 1000, valueSize: 5000)
|
|
|
|
XCTAssertEqual(stats1, stats2, "Identical stats should be equal")
|
|
XCTAssertNotEqual(stats1, stats3, "Different database type should not be equal")
|
|
|
|
// Should work in Set
|
|
let set: Set<NdbDatabaseStats> = [stats1, stats2, stats3]
|
|
XCTAssertEqual(set.count, 2, "Set should contain 2 unique stats")
|
|
}
|
|
|
|
/// Test NdbStats hashable conformance
|
|
func testNdbStatsHashableConformance() {
|
|
let dbStats1 = NdbDatabaseStats(database: .note, keySize: 1000, valueSize: 5000)
|
|
let dbStats2 = NdbDatabaseStats(database: .profile, keySize: 500, valueSize: 2000)
|
|
|
|
let stats1 = NdbStats(databaseStats: [dbStats1, dbStats2])
|
|
let stats2 = NdbStats(databaseStats: [dbStats1, dbStats2])
|
|
let stats3 = NdbStats(databaseStats: [dbStats1])
|
|
|
|
XCTAssertEqual(stats1, stats2, "Identical stats should be equal")
|
|
XCTAssertNotEqual(stats1, stats3, "Different database count should not be equal")
|
|
|
|
// Should work in Set
|
|
let set: Set<NdbStats> = [stats1, stats2, stats3]
|
|
XCTAssertEqual(set.count, 2, "Set should contain 2 unique stats")
|
|
}
|
|
|
|
// MARK: - 6. NdbDatabase Enum Tests
|
|
|
|
/// Test NdbDatabase display names
|
|
func testNdbDatabaseDisplayNames() {
|
|
// Display names include the C enum names in parentheses
|
|
XCTAssertEqual(NdbDatabase.note.displayName, "Notes (NDB_DB_NOTE)", "Note database display name")
|
|
XCTAssertEqual(NdbDatabase.profile.displayName, "Profiles (NDB_DB_PROFILE)", "Profile database display name")
|
|
XCTAssertEqual(NdbDatabase.noteBlocks.displayName, "Note Blocks", "Note blocks display name")
|
|
XCTAssertEqual(NdbDatabase.noteId.displayName, "Note ID Index", "Note ID index display name")
|
|
XCTAssertEqual(NdbDatabase.meta.displayName, "Metadata (NDB_DB_META)", "Metadata display name")
|
|
XCTAssertEqual(NdbDatabase.other.displayName, "Other Data", "Other data display name")
|
|
}
|
|
|
|
/// Test NdbDatabase icons
|
|
func testNdbDatabaseIcons() {
|
|
// Verify each database has an icon (non-empty string)
|
|
XCTAssertFalse(NdbDatabase.note.icon.isEmpty, "Note should have icon")
|
|
XCTAssertFalse(NdbDatabase.profile.icon.isEmpty, "Profile should have icon")
|
|
XCTAssertFalse(NdbDatabase.noteBlocks.icon.isEmpty, "Note blocks should have icon")
|
|
XCTAssertFalse(NdbDatabase.other.icon.isEmpty, "Other should have icon")
|
|
}
|
|
|
|
/// Test NdbDatabase colors
|
|
func testNdbDatabaseColors() {
|
|
// Verify each database has a color assigned
|
|
// We can't easily compare Color values, but we can verify they return Color instances
|
|
_ = NdbDatabase.note.color
|
|
_ = NdbDatabase.profile.color
|
|
_ = NdbDatabase.noteBlocks.color
|
|
_ = NdbDatabase.other.color
|
|
|
|
// If we get here without crashes, colors are working
|
|
XCTAssertTrue(true, "All database colors should be accessible")
|
|
}
|
|
|
|
/// Test NdbDatabase initialization from index
|
|
func testNdbDatabaseFromIndex() {
|
|
// Test valid indices
|
|
let db0 = NdbDatabase(fromIndex: 0)
|
|
XCTAssertNotEqual(db0, .other, "Index 0 should map to a valid database")
|
|
|
|
let db1 = NdbDatabase(fromIndex: 1)
|
|
XCTAssertNotEqual(db1, .other, "Index 1 should map to a valid database")
|
|
|
|
// Test invalid index (should default to .other)
|
|
let dbInvalid = NdbDatabase(fromIndex: 9999)
|
|
XCTAssertEqual(dbInvalid, .other, "Invalid index should default to .other")
|
|
}
|
|
|
|
// MARK: - 7. Integration Tests
|
|
|
|
/// Test complete storage stats flow with real-ish data
|
|
func testStorageStatsIntegrationFlow() async throws {
|
|
// This test verifies the entire flow works end-to-end
|
|
// We use actual calculation but don't assert specific values
|
|
|
|
let stats = try await StorageStatsManager.shared.calculateStorageStats(ndb: nil)
|
|
|
|
// Verify structure
|
|
XCTAssertNotNil(stats, "Stats should be calculated")
|
|
|
|
// Verify all components are accessible
|
|
let _ = stats.nostrdbSize
|
|
let _ = stats.snapshotSize
|
|
let _ = stats.imageCacheSize
|
|
let _ = stats.totalSize
|
|
|
|
// Verify percentage calculation works
|
|
if stats.totalSize > 0 {
|
|
let percentage = stats.percentage(for: stats.nostrdbSize)
|
|
XCTAssertGreaterThanOrEqual(percentage, 0.0, "Percentage should be non-negative")
|
|
XCTAssertLessThanOrEqual(percentage, 100.0, "Percentage should not exceed 100%")
|
|
}
|
|
|
|
// Verify formatting works
|
|
let formatted = StorageStatsManager.formatBytes(stats.totalSize)
|
|
XCTAssertFalse(formatted.isEmpty, "Formatted size should not be empty")
|
|
}
|
|
|
|
/// Test concurrent stats calculations (thread safety)
|
|
func testConcurrentStatsCalculations() async throws {
|
|
let iterations = 5
|
|
|
|
// Launch multiple concurrent calculations
|
|
try await withThrowingTaskGroup(of: StorageStats.self) { group in
|
|
for _ in 0..<iterations {
|
|
group.addTask {
|
|
return try await StorageStatsManager.shared.calculateStorageStats(ndb: nil)
|
|
}
|
|
}
|
|
|
|
var results: [StorageStats] = []
|
|
for try await stats in group {
|
|
results.append(stats)
|
|
}
|
|
|
|
XCTAssertEqual(results.count, iterations, "Should complete all calculations")
|
|
|
|
// All results should have valid structure
|
|
for stats in results {
|
|
XCTAssertGreaterThanOrEqual(stats.nostrdbSize, 0, "NostrDB size should be valid")
|
|
XCTAssertGreaterThanOrEqual(stats.snapshotSize, 0, "Snapshot size should be valid")
|
|
XCTAssertGreaterThanOrEqual(stats.imageCacheSize, 0, "Cache size should be valid")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test storage stats with extreme UInt64 values, including sum at UInt64 boundary (no overflow)
|
|
func testStorageStatsExtremeValues() {
|
|
// Case: Sum at UInt64 boundary (no overflow)
|
|
// UInt64.max - 2 + 1 + 1 == UInt64.max
|
|
let maxStats = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: UInt64.max - 2,
|
|
snapshotSize: 1,
|
|
imageCacheSize: 1
|
|
)
|
|
// Verify correct summation at UInt64 boundary
|
|
XCTAssertEqual(maxStats.totalSize, UInt64.max, "Total should be exactly UInt64.max at boundary; no overflow should occur")
|
|
|
|
// Verify percentage calculation for each component
|
|
XCTAssertEqual(maxStats.percentage(for: UInt64.max - 2), (Double(UInt64.max - 2) / Double(UInt64.max)) * 100.0, accuracy: 0.0001)
|
|
XCTAssertEqual(maxStats.percentage(for: 1), (1.0 / Double(UInt64.max)) * 100.0, accuracy: 0.0001)
|
|
|
|
// All zeros case (already tested elsewhere, but included for completeness)
|
|
let zeroStats = StorageStats(
|
|
nostrdbDetails: nil,
|
|
nostrdbSize: 0,
|
|
snapshotSize: 0,
|
|
imageCacheSize: 0
|
|
)
|
|
XCTAssertEqual(zeroStats.totalSize, 0, "Zero stats should have zero total")
|
|
XCTAssertEqual(zeroStats.percentage(for: 0), 0.0, "Zero percentage for zero total")
|
|
|
|
// If overflow handling should be explicitly tested, add a comment. With current implementation, overflow cannot occur for UInt64 sums with three terms.
|
|
// If more than three terms or arbitrary user input are ever summed, consider adding explicit overflow guards.
|
|
}
|
|
|
|
/// Test byte formatter with various edge cases
|
|
func testFormatBytesEdgeCases() {
|
|
// Powers of 1024
|
|
let formatted1K = StorageStatsManager.formatBytes(1024)
|
|
XCTAssertFalse(formatted1K.isEmpty, "Should format 1KB")
|
|
|
|
let formatted1M = StorageStatsManager.formatBytes(1024 * 1024)
|
|
XCTAssertFalse(formatted1M.isEmpty, "Should format 1MB")
|
|
|
|
let formatted1G = StorageStatsManager.formatBytes(1024 * 1024 * 1024)
|
|
XCTAssertFalse(formatted1G.isEmpty, "Should format 1GB")
|
|
|
|
// Odd values
|
|
let formatted999 = StorageStatsManager.formatBytes(999)
|
|
XCTAssertFalse(formatted999.isEmpty, "Should format 999 bytes")
|
|
|
|
let formatted1023 = StorageStatsManager.formatBytes(1023)
|
|
XCTAssertFalse(formatted1023.isEmpty, "Should format 1023 bytes")
|
|
}
|
|
}
|
|
|