-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Power-Up System #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4a7d83c
ebf48bd
26dbd6a
99defdc
c2de903
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||||||||||||||
| import 'package:cloud_firestore/cloud_firestore.dart'; | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Power-Up Service | ||||||||||||||||||
| /// Manages in-game power-ups: shield, multiplier, slow-time. | ||||||||||||||||||
| /// Handles activation, expiry, and stacking logic. | ||||||||||||||||||
| class PowerUpService { | ||||||||||||||||||
| final FirebaseFirestore _firestore = FirebaseFirestore.instance; | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Critical | ⚡ Quick win
Recommendation:
- final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Suggested change
🤖 Prompt for AI Agentsfinal FirebaseFirestore _firestore = FirebaseFirestore.instance; final PowerUpRepository _repo; |
||||||||||||||||||
|
|
||||||||||||||||||
| /// Activate a power-up for a user | ||||||||||||||||||
| Future<void> activatePowerUp(String userId, String powerUpId, int durationSeconds) async { | ||||||||||||||||||
| final now = DateTime.now(); | ||||||||||||||||||
| final expiresAt = now.add(Duration(seconds: durationSeconds)); | ||||||||||||||||||
|
|
||||||||||||||||||
| await _firestore.collection('active_power_ups').doc('$userId-$powerUpId').set({ | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 Major | ⚡ Quick win
Recommendation:
- await _firestore.collection('active_power_ups').doc('$userId-$powerUpId').set({
'userId': userId,
'powerUpId': powerUpId,
'activatedAt': now,
'expiresAt': expiresAt,
// ...
Suggested change
🤖 Prompt for AI Agentsawait _firestore.collection('active_power_ups').doc('$userId-$powerUpId').set({ await _powerUpRepository.saveActivePowerUp(userId, powerUpId, now, expiresAt); |
||||||||||||||||||
| 'userId': userId, | ||||||||||||||||||
| 'powerUpId': powerUpId, | ||||||||||||||||||
| 'activatedAt': now, | ||||||||||||||||||
| 'expiresAt': expiresAt, | ||||||||||||||||||
| 'isActive': true, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Deduct from inventory | ||||||||||||||||||
| await _firestore.collection('users').doc(userId).update({ | ||||||||||||||||||
| 'inventory.$powerUpId': FieldValue.increment(-1), | ||||||||||||||||||
| }); | ||||||||||||||||||
|
Comment on lines
+10
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate and print relevant sections from power_up_service.dart
FILE="lib/services/power_up_service.dart"
echo "== File: $FILE =="
if [ ! -f "$FILE" ]; then
echo "Missing $FILE"
exit 1
fi
# Show line ranges around the methods mentioned
python3 - <<'PY'
import itertools, os
path="lib/services/power_up_service.dart"
with open(path,'r',encoding='utf-8') as f:
lines=f.readlines()
def show(start,end):
for i in range(start-1,end):
if i<0 or i>=len(lines): continue
print(f"{i+1:4d}: {lines[i].rstrip()}")
print("TOTAL LINES:",len(lines))
# Heuristically show sections that likely include activate/isPowerUpActive/grant/getActivePowerUps/purchase
ranges=[(1,120),(120,240)]
for s,e in ranges:
print("\n--- lines",s,"-",e,"---")
show(s,e)
PY
# Also search for specific patterns
echo
echo "== grep: print / runTransaction / transaction / isActive / expiresAt / cost / coins =="
rg -n "print\(|runTransaction|transaction\(|isActive|expiresAt|purchasePowerUp|activatePowerUp|getActivePowerUps|grantPowerUp|coins|cost" "$FILE" || trueRepository: hexivine/volt-rush Length of output: 5088 Fix atomicity + state validation for power-up activation/purchase/inventory
Suggested fix (activatePowerUp) Future<void> activatePowerUp(String userId, String powerUpId, int durationSeconds) async {
+ if (durationSeconds <= 0) {
+ throw ArgumentError.value(durationSeconds, 'durationSeconds', 'must be > 0');
+ }
final now = DateTime.now();
final expiresAt = now.add(Duration(seconds: durationSeconds));
-
- await _firestore.collection('active_power_ups').doc('$userId-$powerUpId').set({
- 'userId': userId,
- 'powerUpId': powerUpId,
- 'activatedAt': now,
- 'expiresAt': expiresAt,
- 'isActive': true,
- });
-
- // Deduct from inventory
- await _firestore.collection('users').doc(userId).update({
- 'inventory.$powerUpId': FieldValue.increment(-1),
- });
+ final userRef = _firestore.collection('users').doc(userId);
+ final activeRef = _firestore.collection('active_power_ups').doc('$userId-$powerUpId');
+
+ await _firestore.runTransaction((tx) async {
+ final userSnap = await tx.get(userRef);
+ final inventory = (userSnap.data()?['inventory'] as Map<String, dynamic>?) ?? const {};
+ final owned = (inventory[powerUpId] as num?)?.toInt() ?? 0;
+ if (owned <= 0) {
+ throw StateError('No $powerUpId inventory available');
+ }
+
+ tx.set(activeRef, {
+ 'userId': userId,
+ 'powerUpId': powerUpId,
+ 'activatedAt': now,
+ 'expiresAt': expiresAt,
+ 'isActive': true,
+ });
+ tx.update(userRef, {
+ 'inventory.$powerUpId': FieldValue.increment(-1),
+ });
+ });🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| print('Power-up $powerUpId activated for $userId (expires in ${durationSeconds}s)'); | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Trivial | ⚡ Quick win
Recommendation:
- print('Power-up $powerUpId activated for $userId (expires in ${durationSeconds}s)');
Suggested change
🤖 Prompt for AI Agentsprint('Power-up $powerUpId activated for logger.info('Power-up $powerUpId activated for |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Check if a power-up is currently active | ||||||||||||||||||
| Future<bool> isPowerUpActive(String userId, String powerUpId) async { | ||||||||||||||||||
| final doc = await _firestore | ||||||||||||||||||
| .collection('active_power_ups') | ||||||||||||||||||
| .doc('$userId-$powerUpId') | ||||||||||||||||||
| .get(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (!doc.exists) return false; | ||||||||||||||||||
|
|
||||||||||||||||||
| final data = doc.data()!; | ||||||||||||||||||
| final expiresAt = (data['expiresAt'] as dynamic).toDate(); | ||||||||||||||||||
| return DateTime.now().isBefore(expiresAt); | ||||||||||||||||||
|
Comment on lines
+31
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
At Line 41, the method returns true based only on Suggested fix if (!doc.exists) return false;
- final data = doc.data()!;
- final expiresAt = (data['expiresAt'] as dynamic).toDate();
- return DateTime.now().isBefore(expiresAt);
+ final data = doc.data()!;
+ final isActive = data['isActive'] == true;
+ if (!isActive) return false;
+ final expiresAt = (data['expiresAt'] as dynamic).toDate();
+ return DateTime.now().isBefore(expiresAt);🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Grant a power-up to user's inventory (e.g., from reward or purchase) | ||||||||||||||||||
| Future<void> grantPowerUp(String userId, String powerUpId, int quantity) async { | ||||||||||||||||||
| final userRef = _firestore.collection('users').doc(userId); | ||||||||||||||||||
|
|
||||||||||||||||||
| await userRef.update({ | ||||||||||||||||||
| 'inventory.$powerUpId': FieldValue.increment(quantity), | ||||||||||||||||||
| 'totalPowerUpsEarned': FieldValue.increment(quantity), | ||||||||||||||||||
| 'lastPowerUpAt': DateTime.now(), | ||||||||||||||||||
| }); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Get all active power-ups for a user | ||||||||||||||||||
| Future<List<Map<String, dynamic>>> getActivePowerUps(String userId) async { | ||||||||||||||||||
| final snapshot = await _firestore | ||||||||||||||||||
| .collection('active_power_ups') | ||||||||||||||||||
| .where('userId', isEqualTo: userId) | ||||||||||||||||||
| .where('isActive', isEqualTo: true) | ||||||||||||||||||
| .get(); | ||||||||||||||||||
|
|
||||||||||||||||||
| final now = DateTime.now(); | ||||||||||||||||||
| final active = <Map<String, dynamic>>[]; | ||||||||||||||||||
|
|
||||||||||||||||||
| for (final doc in snapshot.docs) { | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Critical
Recommendation:
- for (final doc in snapshot.docs) {
final data = doc.data();
final expiresAt = (data['expiresAt'] as dynamic).toDate();
if (now.isBefore(expiresAt)) {
active.add(data);
// ...🤖 Prompt for AI Agentsfor (final doc in snapshot.docs) { final batch = _firestore.batch(); |
||||||||||||||||||
| final data = doc.data(); | ||||||||||||||||||
| final expiresAt = (data['expiresAt'] as dynamic).toDate(); | ||||||||||||||||||
| if (now.isBefore(expiresAt)) { | ||||||||||||||||||
| active.add(data); | ||||||||||||||||||
| } else { | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Minor
Recommendation:
- } else {
// Expired — mark as inactive
await doc.reference.update({'isActive': false});
}
Suggested change
🤖 Prompt for AI Agents} else { } else { |
||||||||||||||||||
| // Expired — mark as inactive | ||||||||||||||||||
| await doc.reference.update({'isActive': false}); | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Critical | ⚡ Quick win
Recommendation:
- await doc.reference.update({'isActive': false});
Suggested change
🤖 Prompt for AI Agentsawait doc.reference.update({'isActive': false}); await doc.reference.update({'isActive': false}); |
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return active; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Purchase a power-up with in-game coins | ||||||||||||||||||
| Future<bool> purchasePowerUp(String userId, String powerUpId, int cost) async { | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 Major
Recommendation:
- Future<bool> purchasePowerUp(String userId, String powerUpId, int cost) async {
final userDoc = await _firestore.collection('users').doc(userId).get();
final coins = userDoc.data()?['coins'] ?? 0;
if (coins < cost) return false;
await _firestore.collection('users').doc(userId).update({
// ...
Suggested change
🤖 Prompt for AI AgentsFuture purchasePowerUp(String userId, String powerUpId, int cost) async { // Delegated to a dedicated purchase service to keep responsibilities separate |
||||||||||||||||||
| final userDoc = await _firestore.collection('users').doc(userId).get(); | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Critical | ⚡ Quick win
Recommendation:
- final userDoc = await _firestore.collection('users').doc(userId).get();
Suggested change
🤖 Prompt for AI Agentsfinal userDoc = await _firestore.collection('users').doc(userId).get(); final userDoc = await _firestore.collection('users').doc(userId).get(); |
||||||||||||||||||
| final coins = userDoc.data()?['coins'] ?? 0; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (coins < cost) return false; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Deduct coins and grant power-up | ||||||||||||||||||
| await _firestore.collection('users').doc(userId).update({ | ||||||||||||||||||
| 'coins': FieldValue.increment(-cost), | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| await grantPowerUp(userId, powerUpId, 1); | ||||||||||||||||||
| return true; | ||||||||||||||||||
|
Comment on lines
+81
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate the file and print the relevant portion around purchasePowerUp
ls -la lib/services/power_up_service.dart
sed -n '1,200p' lib/services/power_up_service.dart | nl -ba | sed -n '60,140p'
# Show the full purchasePowerUp method with wider context
rg -n "purchasePowerUp\\(" -S lib/services/power_up_service.dart
start=$(rg -n "purchasePowerUp\\(" -S lib/services/power_up_service.dart | cut -d: -f1 | head -n1)
end=$((start+80))
sed -n "${start},${end}p" lib/services/power_up_service.dart | nl -ba
# Find grantPowerUp implementation
rg -n "grantPowerUp\\(" -S lib/services/power_up_service.dart
grant_start=$(rg -n "grantPowerUp\\(" -S lib/services/power_up_service.dart | head -n1 | cut -d: -f1)
grant_end=$((grant_start+120))
sed -n "${grant_start},${grant_end}p" lib/services/power_up_service.dart | nl -ba
# Check if there are any other coin deductions/transactions patterns elsewhere in the file
rg -n "runTransaction|transaction|FieldValue\\.increment\\(-|coins" -S lib/services/power_up_service.dartRepository: hexivine/volt-rush Length of output: 207 🏁 Script executed: # (script already prepared above)Repository: hexivine/volt-rush Length of output: 44 🏁 Script executed: #!/bin/bash
set -euo pipefail
ls -la lib/services/power_up_service.dart
# Print a window around the indicated line range
sed -n '1,220p' lib/services/power_up_service.dart | nl -ba | sed -n '60,160p'
# Show purchasePowerUp with context
rg -n "Future<bool>\\s+purchasePowerUp\\(" -S lib/services/power_up_service.dart
start=$(rg -n "Future<bool>\\s+purchasePowerUp\\(" -S lib/services/power_up_service.dart | cut -d: -f1 | head -n1)
end=$((start+120))
sed -n "${start},${end}p" lib/services/power_up_service.dart | nl -ba
# Show grantPowerUp implementation
rg -n "Future<.*>\\s+grantPowerUp\\(" -S lib/services/power_up_service.dart
grant_start=$(rg -n "Future<.*>\\s+grantPowerUp\\(" -S lib/services/power_up_service.dart | cut -d: -f1 | head -n1)
grant_end=$((grant_start+180))
sed -n "${grant_start},${grant_end}p" lib/services/power_up_service.dart | nl -ba
# Search for transactions and coin/inventory fields in this service
rg -n "runTransaction|runTransaction\\(|transaction\\(|FieldValue\\.increment\\(-|\\['coins'\\]|'coins'|grantPowerUp\\(" -S lib/services/power_up_service.dartRepository: hexivine/volt-rush Length of output: 207 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="lib/services/power_up_service.dart"
echo "== File =="
wc -l "$FILE"
echo "== purchasePowerUp region (guessing around lines 70-130) =="
sed -n '60,130p' "$FILE"
echo "== grantPowerUp region (search then print nearby) =="
grant_line=$(rg -n "grantPowerUp\\(" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -z "${grant_line:-}" ]; then
echo "grantPowerUp not found"
else
start=$((grant_line-20))
end=$((grant_line+160))
sed -n "${start},${end}p" "$FILE"
fi
echo "== Firestore transaction usage in this file =="
rg -n "runTransaction\\(|transaction\\(" "$FILE" || true
echo "== cost validation / input checks in purchasePowerUp =="
# Extract purchasePowerUp function body roughly (from signature to next closing brace)
sig_line=$(rg -n "Future<bool>\\s+purchasePowerUp\\(" "$FILE" | cut -d: -f1 | head -n1)
echo "purchasePowerUp signature line: $sig_line"
sed -n "$((sig_line-5)),$((sig_line+140))p" "$FILE"Repository: hexivine/volt-rush Length of output: 4092 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "purchasePowerUp\\(" -S . || trueRepository: hexivine/volt-rush Length of output: 184 Fix
Use 🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| diff --git a/test/services/daily_challenge_service_test.dart b/test/services/daily_challenge_service_test.dart | ||
| new file mode 100644 | ||
| index 0000000..b1d65d7 | ||
| --- /dev/null | ||
| +++ b/test/services/daily_challenge_service_test.dart | ||
| @@ -0,0 +1,163 @@ | ||
| +import 'package:flutter_test/flutter_test.dart'; | ||
| +import 'package:mockito/mockito.dart'; | ||
| +import 'package:cloud_firestore/cloud_firestore.dart'; | ||
| +import 'package:volt_rush/services/daily_challenge_service.dart'; | ||
| + | ||
| +class MockFirestore extends Mock implements FirebaseFirestore {} | ||
| +class MockCollectionReference extends Mock implements CollectionReference {} | ||
| +class MockDocumentReference extends Mock implements DocumentReference {} | ||
| +class MockQuerySnapshot extends Mock implements QuerySnapshot {} | ||
| +class MockQueryDocumentSnapshot extends Mock implements QueryDocumentSnapshot {} | ||
| + | ||
| +void main() { | ||
| + late DailyChallengeService service; | ||
| + late MockFirestore mockFirestore; | ||
| + late MockCollectionReference mockCollection; | ||
| + late MockDocumentReference mockDocRef; | ||
| + late MockQuerySnapshot mockQuerySnapshot; | ||
| + late MockQueryDocumentSnapshot mockQueryDocSnapshot; | ||
| + | ||
| + setUp(() { | ||
| + mockFirestore = MockFirestore(); | ||
| + mockCollection = MockCollectionReference(); | ||
| + mockDocRef = MockDocumentReference(); | ||
| + mockQuerySnapshot = MockQuerySnapshot(); | ||
| + mockQueryDocSnapshot = MockQueryDocumentSnapshot(); | ||
| + service = DailyChallengeService(); | ||
| + service._firestore = mockFirestore; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether `_firestore` is private and whether a public injection path exists.
# Read-only reconnaissance only.
set -euo pipefail
echo "== Locate DailyChallengeService declaration =="
fd -i "daily_challenge_service.dart" lib test
echo
echo "== Find _firestore declarations/usages in service file(s) =="
rg -n --type=dart -C3 '\b_firestore\b|class\s+DailyChallengeService|DailyChallengeService\s*\(' lib test
echo
echo "== Find any public setter/constructor parameter for Firestore injection =="
rg -n --type=dart -C3 'set\s+firestore|FirebaseFirestore\s+\w+|DailyChallengeService\s*\([^)]*FirebaseFirestore' lib test
echo
echo "== Find test-side private member access =="
rg -n --type=dart -C2 'service\._firestore\s*=' testRepository: hexivine/volt-rush Length of output: 6698 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Find DailyChallengeService declaration =="
rg -n --type:dart -S "class\s+DailyChallengeService\b|DailyChallengeService\s*\(" . || true
echo
echo "== Locate the test file mentioning completeChallenge =="
rg -n --type:dart -S "completeChallenge" test lib . || true
echo
echo "== Search for private member access to _firestore in tests =="
rg -n --type:dart -S "service\._firestore\s*=" test . || true
echo
echo "== Search for _firestore field declarations/usages in DailyChallengeService-related files =="
rg -n --type:dart -S "_firestore\b" lib test . || trueRepository: hexivine/volt-rush Length of output: 2083 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Locate DailyChallenge files by name (case-insensitive) =="
fd -i "daily_challenge.*\.dart" . || true
echo
echo "== Locate DailyChallengeService test file =="
fd -i "daily_challenge_service_test\.dart" . || true
echo
echo "== Locate DailyChallengeService implementation file =="
fd -i "daily_challenge_service\.dart" lib . || true
echo
echo "== Search for 'class DailyChallengeService' =="
rg -n -t dart -S "class\s+DailyChallengeService\b" . || true
echo
echo "== Search for '_firestore' usage/decls across Dart =="
rg -n -t dart -S "_firestore\b" lib test . || true
echo
echo "== Search for 'service._firestore =' assignment in tests =="
rg -n -t dart -S "service\._firestore\s*=" test . || true
echo
echo "== Search for completeChallenge in Dart =="
rg -n -t dart -S "completeChallenge" test lib . || trueRepository: hexivine/volt-rush Length of output: 3207 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Search for DailyChallenge* symbols =="
rg -n -t dart -S "DailyChallenge" . || true
echo
echo "== Search for daily_challenge* filenames/dirs =="
fd -i "daily_challenge" . || trueRepository: hexivine/volt-rush Length of output: 153 🏁 Script executed: #!/bin/bash
set -euo pipefail
ls -la
echo "== Does test_pr_diff.txt exist? =="
ls -la test_pr_diff.txt || true
echo
echo "== Show test_pr_diff.txt (first 120 lines) =="
sed -n '1,120p' test_pr_diff.txt || true
echo
echo "== Show around line 33 =="
nl -ba test_pr_diff.txt | sed -n '20,60p' || trueRepository: hexivine/volt-rush Length of output: 6860 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== List lib/services =="
ls -la lib/services || true
echo
echo "== Locate daily_challenge_service.dart file =="
fd -i "daily_challenge_service\.dart" lib test . || true
echo
echo "== Show daily_challenge_service implementation header (first 120 lines) =="
if [ -f lib/services/daily_challenge_service.dart ]; then
nl -ba lib/services/daily_challenge_service.dart | sed -n '1,160p'
else
echo "MISSING: lib/services/daily_challenge_service.dart"
fi
echo
echo "== Search within daily_challenge_service.dart for _firestore and visibility =="
if [ -f lib/services/daily_challenge_service.dart ]; then
rg -n -S "_firestore" lib/services/daily_challenge_service.dart || true
rg -n -S "class\s+DailyChallengeService\b" lib/services/daily_challenge_service.dart || true
fiRepository: hexivine/volt-rush Length of output: 597 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Locate any daily_challenge_service*.dart in repo =="
fd -i "daily_challenge_service.*\.dart" . || true
echo
echo "== Search for 'daily_challenge_service.dart' references =="
rg -n -S -t dart "daily_challenge_service\.dart" . || true
echo
echo "== Search for DailyChallengeService symbol =="
rg -n -S -t dart "DailyChallengeService" . || true
echo
echo "== Tree lib/ (top 4 levels) =="
find lib -maxdepth 4 -type f | sed 's|^\./||' | head -n 200Repository: hexivine/volt-rush Length of output: 876 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Show added test file header + setUp() =="
sed -n '1,80p' test_pr_diff.txt
echo
echo "== Show where '_firestore' is used in the diff =="
rg -n "_firestore" test_pr_diff.txt
echo
echo "== Show completeChallenge test block =="
rg -n "group\\('completeChallenge'\\)" -n test_pr_diff.txt || true
sed -n '80,190p' test_pr_diff.txtRepository: hexivine/volt-rush Length of output: 7062 Fix compilation: add missing DailyChallengeService implementation and avoid mutating private
Diff snippet+ service._firestore = mockFirestore;🤖 Prompt for AI Agents |
||
| + }); | ||
| + | ||
| + group('getTodayChallenge', () { | ||
| + test('should return null when no challenge exists', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.where('date', isEqualTo: anyNamed('isEqualTo'))).thenReturn(mockCollection); | ||
| + when(mockCollection.where('userId', isEqualTo: anyNamed('isEqualTo'))).thenReturn(mockCollection); | ||
| + when(mockCollection.get()).thenAnswer((_) async => mockQuerySnapshot); | ||
| + when(mockQuerySnapshot.docs).thenReturn([]); | ||
| + | ||
| + // act | ||
| + final result = await service.getTodayChallenge('user1'); | ||
| + | ||
| + // assert | ||
| + expect(result, isNull); | ||
| + }); | ||
| + | ||
| + test('should return challenge data when challenge exists', () async { | ||
| + // arrange | ||
| + final challengeData = {'challenge': 'data'}; | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.where('date', isEqualTo: anyNamed('isEqualTo'))).thenReturn(mockCollection); | ||
| + when(mockCollection.where('userId', isEqualTo: anyNamed('isEqualTo'))).thenReturn(mockCollection); | ||
| + when(mockCollection.get()).thenAnswer((_) async => mockQuerySnapshot); | ||
| + when(mockQuerySnapshot.docs).thenReturn([mockQueryDocSnapshot]); | ||
| + when(mockQueryDocSnapshot.data()).thenReturn(challengeData); | ||
| + | ||
| + // act | ||
| + final result = await service.getTodayChallenge('user1'); | ||
| + | ||
| + // assert | ||
| + expect(result, equals(challengeData)); | ||
| + }); | ||
| + | ||
| + test('should handle empty userId', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.where('date', isEqualTo: anyNamed('isEqualTo'))).thenReturn(mockCollection); | ||
| + when(mockCollection.where('userId', isEqualTo: anyNamed('isEqualTo'))).thenReturn(mockCollection); | ||
| + when(mockCollection.get()).thenAnswer((_) async => mockQuerySnapshot); | ||
| + when(mockQuerySnapshot.docs).thenReturn([]); | ||
| + | ||
| + // act | ||
| + final result = await service.getTodayChallenge(''); | ||
| + | ||
| + // assert | ||
| + expect(result, isNull); | ||
| + }); | ||
| + }); | ||
| + | ||
| + group('completeChallenge', () { | ||
| + test('should update challenge and user data', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.doc(any)).thenReturn(mockDocRef); | ||
| + when(mockDocRef.update(any)).thenAnswer((_) async => null); | ||
| + when(mockFirestore.collection('users')).thenReturn(mockCollection); | ||
| + when(mockCollection.doc(any)).thenReturn(mockDocRef); | ||
| + when(mockDocRef.update(any)).thenAnswer((_) async => null); | ||
| + | ||
| + // act | ||
| + await service.completeChallenge('user1', 'challenge1', 100); | ||
| + | ||
| + // assert | ||
| + verify(mockDocRef.update(any)).called(2); | ||
| + }); | ||
| + | ||
| + test('should handle negative score', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.doc(any)).thenReturn(mockDocRef); | ||
| + when(mockDocRef.update(any)).thenAnswer((_) async => null); | ||
| + when(mockFirestore.collection('users')).thenReturn(mockCollection); | ||
| + when(mockCollection.doc(any)).thenReturn(mockDocRef); | ||
| + when(mockDocRef.update(any)).thenAnswer((_) async => null); | ||
| + | ||
| + // act | ||
| + await service.completeChallenge('user1', 'challenge1', -100); | ||
| + | ||
| + // assert | ||
| + verify(mockDocRef.update(any)).called(2); | ||
| + }); | ||
| + | ||
| + test('should handle empty userId', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.doc(any)).thenReturn(mockDocRef); | ||
| + when(mockDocRef.update(any)).thenAnswer((_) async => null); | ||
| + when(mockFirestore.collection('users')).thenReturn(mockCollection); | ||
| + when(mockCollection.doc(any)).thenReturn(mockDocRef); | ||
| + when(mockDocRef.update(any)).thenAnswer((_) async => null); | ||
| + | ||
| + // act | ||
| + await service.completeChallenge('', 'challenge1', 100); | ||
| + | ||
| + // assert | ||
| + verify(mockDocRef.update(any)).called(2); | ||
| + }); | ||
| + }); | ||
| + | ||
| + group('generateNextChallenge', () { | ||
| + test('should add new challenge to Firestore', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.add(any)).thenAnswer((_) async => mockDocRef); | ||
| + | ||
| + // act | ||
| + await service.generateNextChallenge('user1'); | ||
| + | ||
| + // assert | ||
| + verify(mockCollection.add(any)).called(1); | ||
| + }); | ||
| + | ||
| + test('should handle empty userId', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.add(any)).thenAnswer((_) async => mockDocRef); | ||
| + | ||
| + // act | ||
| + await service.generateNextChallenge(''); | ||
| + | ||
| + // assert | ||
| + verify(mockCollection.add(any)).called(1); | ||
| + }); | ||
| + | ||
| + test('should handle Firestore errors', () async { | ||
| + // arrange | ||
| + when(mockFirestore.collection('daily_challenges')).thenReturn(mockCollection); | ||
| + when(mockCollection.add(any)).thenThrow(Exception('Firestore error')); | ||
| + | ||
| + // act & assert | ||
| + expect(() => service.generateNextChallenge('user1'), throwsException); | ||
| + }); | ||
| + }); | ||
| +} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Critical | ⚡ Quick win
What's happening:
Recommendation:
- await _firestore.collection('active_power_ups').doc('$userId-$powerUpId').set({...});🤖 Prompt for AI Agents
await _firestore.collection('active_power_ups').doc('$userId-$powerUpId').set({...});
await firestore.collection('active_power_ups').doc('${userId}$powerUpId').set({...});