Umfassende Erweiterung, Lokalisierung, Besuchsbewertung

This commit is contained in:
2026-04-18 20:30:48 +02:00
parent 0b35403096
commit e75141d23c
54 changed files with 9332 additions and 378 deletions
+163
View File
@@ -0,0 +1,163 @@
import Testing
import Foundation
@testable import nahbar
// MARK: - SubscriptionTier Tests
@Suite("SubscriptionTier Enum")
struct SubscriptionTierTests {
@Test("Genau 2 Tiers vorhanden")
func allCasesCount() {
#expect(SubscriptionTier.allCases.count == 2)
}
@Test("Pro productID ist 'profeatures'")
func proProductID() {
#expect(SubscriptionTier.pro.productID == "profeatures")
}
@Test("Max productID ist 'maxfeatures'")
func maxProductID() {
#expect(SubscriptionTier.max.productID == "maxfeatures")
}
@Test("Pro displayName ist 'Pro'")
func proDisplayName() {
#expect(SubscriptionTier.pro.displayName == "Pro")
}
@Test("Max displayName ist 'Max'")
func maxDisplayName() {
#expect(SubscriptionTier.max.displayName == "Max")
}
@Test("productIDs sind einzigartig")
func productIDsAreUnique() {
let ids = SubscriptionTier.allCases.map { $0.productID }
#expect(Set(ids).count == SubscriptionTier.allCases.count)
}
@Test("displayNames sind einzigartig")
func displayNamesAreUnique() {
let names = SubscriptionTier.allCases.map { $0.displayName }
#expect(Set(names).count == SubscriptionTier.allCases.count)
}
@Test("productIDs sind nicht leer")
func productIDsNotEmpty() {
for tier in SubscriptionTier.allCases {
#expect(!tier.productID.isEmpty)
}
}
}
// MARK: - AI Free Query Counter Tests
// Tests laufen serialisiert, da alle auf UserDefaults.standard mit gleichem Key schreiben
@Suite("AIAnalysisService Gratis-Abfragen", .serialized)
struct AIFreeQueryTests {
init() {
AIAnalysisService.shared.freeQueriesUsed = 0
}
@Test("Limit ist genau 3")
func limitIsThree() {
#expect(AIAnalysisService.freeQueryLimit == 3)
}
@Test("Initial: Abfragen verfügbar, remaining = 3")
func initialState() {
AIAnalysisService.shared.freeQueriesUsed = 0
#expect(AIAnalysisService.shared.hasFreeQueriesLeft == true)
#expect(AIAnalysisService.shared.freeQueriesRemaining == 3)
}
@Test("consumeFreeQuery erhöht Zähler um 1")
func consumeIncrementsCounter() {
AIAnalysisService.shared.freeQueriesUsed = 0
AIAnalysisService.shared.consumeFreeQuery()
#expect(AIAnalysisService.shared.freeQueriesUsed == 1)
#expect(AIAnalysisService.shared.freeQueriesRemaining == 2)
}
@Test("Nach 3 Verbrauchungen: hasFreeQueriesLeft == false")
func afterThreeConsumed() {
AIAnalysisService.shared.freeQueriesUsed = 0
AIAnalysisService.shared.consumeFreeQuery()
AIAnalysisService.shared.consumeFreeQuery()
AIAnalysisService.shared.consumeFreeQuery()
#expect(AIAnalysisService.shared.hasFreeQueriesLeft == false)
#expect(AIAnalysisService.shared.freeQueriesRemaining == 0)
}
@Test("Zähler über Limit: remaining bleibt 0, nicht negativ")
func remainingNotNegative() {
AIAnalysisService.shared.freeQueriesUsed = 10
#expect(AIAnalysisService.shared.freeQueriesRemaining == 0)
#expect(AIAnalysisService.shared.hasFreeQueriesLeft == false)
}
@Test("Zähler = limit - 1: noch genau 1 Abfrage verfügbar")
func oneQueryLeft() {
AIAnalysisService.shared.freeQueriesUsed = AIAnalysisService.freeQueryLimit - 1
#expect(AIAnalysisService.shared.hasFreeQueriesLeft == true)
#expect(AIAnalysisService.shared.freeQueriesRemaining == 1)
}
@Test("Zähler = limit: keine Abfragen mehr verfügbar")
func atLimit() {
AIAnalysisService.shared.freeQueriesUsed = AIAnalysisService.freeQueryLimit
#expect(AIAnalysisService.shared.hasFreeQueriesLeft == false)
}
@Test("freeQueriesUsed + freeQueriesRemaining = limit (solange unter limit)")
func usedPlusRemainingEqualsLimit() {
for used in 0...AIAnalysisService.freeQueryLimit {
AIAnalysisService.shared.freeQueriesUsed = used
let remaining = AIAnalysisService.shared.freeQueriesRemaining
#expect(used + remaining == AIAnalysisService.freeQueryLimit)
}
}
}
// MARK: - AppGroup Pro-Status Tests
// Tests laufen serialisiert, da alle dieselbe UserDefaults-Suite nutzen
@Suite("AppGroup Pro-Status", .serialized)
struct AppGroupProStatusTests {
private let testDefaults = UserDefaults(suiteName: "nahbar.test.proStatus")!
init() {
testDefaults.removeObject(forKey: "isPro")
testDefaults.synchronize()
}
@Test("Pro-Status initial false wenn nicht gesetzt")
func proStatusInitiallyFalse() {
testDefaults.removeObject(forKey: "isPro")
#expect(testDefaults.bool(forKey: "isPro") == false)
}
@Test("Pro-Status round-trip: true")
func proStatusRoundTripTrue() {
testDefaults.set(true, forKey: "isPro")
#expect(testDefaults.bool(forKey: "isPro") == true)
}
@Test("Pro-Status round-trip: false")
func proStatusRoundTripFalse() {
testDefaults.set(true, forKey: "isPro")
testDefaults.set(false, forKey: "isPro")
#expect(testDefaults.bool(forKey: "isPro") == false)
}
@Test("Pro-Status nach removeObject ist false")
func proStatusAfterRemoveIsFalse() {
testDefaults.set(true, forKey: "isPro")
testDefaults.removeObject(forKey: "isPro")
#expect(testDefaults.bool(forKey: "isPro") == false)
}
}