Skip to content

Commit 3e9e430

Browse files
committed
test: inject gemini api key environment overide
Fixes tests occasionally failing in CI where `GEMINI_API_KEY` is not present in the system environment by explicitly exposing an `environmentOverride` variable to `BaseSkillCommand` instead of relying completely on `Platform.environment`.
1 parent c575b2b commit 3e9e430

7 files changed

Lines changed: 59 additions & 18 deletions

‎tool/lib/src/commands/base_skill_command.dart‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ abstract class BaseSkillCommand extends Command {
2020
required this.httpClient,
2121
required this.logger,
2222
this.outputDir,
23+
this.environmentOverride,
2324
}) {
2425
argParser
2526
..addOption('skill', help: 'Process only the specified skill by name.')
@@ -44,6 +45,9 @@ abstract class BaseSkillCommand extends Command {
4445

4546
/// The logger for this command.
4647
final Logger logger;
48+
49+
/// Testing override for Platform.environment
50+
final Map<String, String>? environmentOverride;
4751

4852
@override
4953
Future<void> run() async {
@@ -77,7 +81,8 @@ abstract class BaseSkillCommand extends Command {
7781
return;
7882
}
7983

80-
final apiKey = Platform.environment['GEMINI_API_KEY'] ?? 'fake-api-key';
84+
final env = environmentOverride ?? Platform.environment;
85+
final apiKey = env['GEMINI_API_KEY'];
8186
if (apiKey == null) {
8287
logger.severe('GEMINI_API_KEY environment variable not set.');
8388
return;

‎tool/lib/src/commands/generate_skill_command.dart‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import 'base_skill_command.dart';
1414
/// Command to generate skills from a configuration file.
1515
class GenerateSkillCommand extends BaseSkillCommand {
1616
/// Creates a new [GenerateSkillCommand].
17-
GenerateSkillCommand({required super.httpClient, super.outputDir})
18-
: super(logger: Logger('GenerateSkillCommand'));
17+
GenerateSkillCommand({
18+
required super.httpClient,
19+
super.outputDir,
20+
super.environmentOverride,
21+
}) : super(logger: Logger('GenerateSkillCommand'));
1922

2023
@override
2124
String get name => 'generate-skill';

‎tool/lib/src/commands/validate_skill_command.dart‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class ValidateSkillCommand extends BaseSkillCommand {
1717
ValidateSkillCommand({
1818
required super.httpClient,
1919
super.outputDir,
20+
super.environmentOverride,
2021
this.validationDir,
2122
}) : super(logger: Logger('ValidateSkillCommand'));
2223

‎tool/resources/flutter_skills.yaml‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
- name: flutter-architecture
2+
description: Configure Flutter SDK on the user's machine, setup IDEs, and diagnose CLI errors.
3+
instructions: |
4+
Architecture is a broad term that can refer to many topics depending on the context. In the context of Flutter, architecture refers to how to structure, organize, and design your Flutter app to scale as project requirements and teams grow. It is an important part of building a maintainable, resilient, and scalable Flutter app. This guide covers app architecture principles and best practices for building Flutter apps.
5+
6+
Good app architecture provides several benefits to engineering teams and end users. It makes it easier to modify, update, and fix issues over time (maintainability). A well-thought-out application allows more people to contribute to the same codebase concurrently with minimal code conflicts (scalability).
7+
8+
Common architectural principles include separation of concerns, layered architecture, single source of truth, unidirectional data flow, UI as a function of (immutable) state, extensibility, and testability.
9+
10+
Flutter applications should be written in layers. Layered architecture is a software design pattern that organizes an application into distinct layers, each with specific roles and responsibilities. Typically, applications are separated into 2 to 3 layers, depending on complexity. The three common layers of app architecture are the UI layer, logic layer, and data layer.
11+
12+
* **UI layer** (also known as the 'presentation layer') displays data to the user that is exposed by the business logic layer and handles user interaction. In Flutter, this applies to widgets, which are the basic building blocks of a Flutter user interface. You should write reusable, lean widgets that hold as little logic as possible.
13+
* **Logic layer** (commonly known as the 'domain layer') implements core business logic and facilitates interaction between the data layer and UI layer. This layer is optional and only needs to be implemented if your application has complex business logic that happens on the client. Many apps are only concerned with presenting data to a user and allowing the user to change that data (colloquially known as CRUD apps).
14+
* **Data layer** (known as the model in MVVM terminology) is the source of truth for all application data. It handles business data and logic. As the source of truth, it's the only place that application data should be updated. It is responsible for consuming data from various external APIs, exposing that data to the UI, handling events from the UI that require data to be updated, and sending update requests to those external APIs as needed. The data layer typically has two main components: repositories and services.
15+
* **Repositories** are the source of truth for your model data. They contain logic related to that data, such as updating data in response to user events or polling for data from services. Repositories are responsible for synchronizing data when offline capabilities are supported, managing retry logic, and caching data. They transform raw data into domain models, which represent the data the application needs, formatted for consumption by view model classes.
16+
* **Services** are stateless Dart classes that interact with APIs, such as HTTP servers and platform plugins. Their only job is to wrap an external API. Any data your application needs that isn't created inside the application code itself should be fetched from within service classes. There is generally one service class per data source.
17+
18+
Within each layer, you should further separate your application by feature or functionality. For example, your application's authentication logic should be in a different class than the search logic.
19+
20+
The Flutter team's recommended app architecture includes common architectural principles, MVVM and state management, dependency injection, and common design patterns for writing robust Flutter applications.
21+
22+
resources:
23+
- https://docs.flutter.dev/app-architecture
24+
- https://docs.flutter.dev/app-architecture/concepts
25+
- https://docs.flutter.dev/app-architecture/guide
26+
- https://docs.flutter.dev/resources/architectural-overview
27+
- https://docs.flutter.dev/app-architecture/recommendations
28+
- https://docs.flutter.dev/app-architecture/design-patterns
29+
- https://docs.flutter.dev/app-architecture/case-study
30+
- https://docs.flutter.dev/app-architecture/case-study/data-layer
31+
- https://docs.flutter.dev/app-architecture/design-patterns/key-value-data
32+
- https://docs.flutter.dev/learn/pathway/how-flutter-works

‎tool/test/generate_skills_retry_test.dart‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void main() {
7676
return http.Response('Not Found', 404);
7777
});
7878

79-
final command = GenerateSkillCommand(
79+
final command = GenerateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
8080
httpClient: mockClient,
8181
outputDir: tempDir,
8282
);

‎tool/test/generate_skills_test.dart‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ void main() {
8282
return http.Response('Not Found', 404);
8383
});
8484

85-
final command = GenerateSkillCommand(
85+
final command = GenerateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
8686
httpClient: mockClient,
8787
outputDir: tempDir,
8888
);
@@ -157,7 +157,7 @@ void main() {
157157
return http.Response('Error', 500);
158158
});
159159

160-
final command = GenerateSkillCommand(
160+
final command = GenerateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
161161
httpClient: mockClient,
162162
outputDir: tempDir,
163163
);
@@ -224,7 +224,7 @@ void main() {
224224
return http.Response('Error', 500);
225225
});
226226

227-
final command = GenerateSkillCommand(
227+
final command = GenerateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
228228
httpClient: mockClient,
229229
outputDir: skillsDir,
230230
);
@@ -301,7 +301,7 @@ void main() {
301301
return http.Response('Error', 500);
302302
});
303303

304-
final command = GenerateSkillCommand(
304+
final command = GenerateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
305305
httpClient: mockClient,
306306
outputDir: skillsDir,
307307
);

‎tool/test/validate_skills_test.dart‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ void main() {
6666

6767
runner = CommandRunner('skills', 'Test runner')
6868
..addCommand(
69-
ValidateSkillCommand(
69+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
7070
outputDir: skillsDir,
7171
validationDir: validationDir,
7272
httpClient: mockClient,
@@ -135,7 +135,7 @@ void main() {
135135

136136
runner = CommandRunner('skills', 'Test runner')
137137
..addCommand(
138-
ValidateSkillCommand(
138+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
139139
outputDir: skillsDir,
140140
validationDir: validationDir,
141141
httpClient: mockClient,
@@ -179,7 +179,7 @@ void main() {
179179

180180
runner = CommandRunner('skills', 'Test runner')
181181
..addCommand(
182-
ValidateSkillCommand(
182+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
183183
outputDir: skillsDir,
184184
validationDir: validationDir,
185185
httpClient: mockClient,
@@ -199,7 +199,7 @@ void main() {
199199

200200
runner = CommandRunner('skills', 'Test runner')
201201
..addCommand(
202-
ValidateSkillCommand(outputDir: skillsDir, httpClient: mockClient),
202+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},outputDir: skillsDir, httpClient: mockClient),
203203
);
204204

205205
await IOOverrides.runZoned(() async {
@@ -240,7 +240,7 @@ void main() {
240240

241241
runner = CommandRunner('skills', 'Test runner')
242242
..addCommand(
243-
ValidateSkillCommand(outputDir: skillsDir, httpClient: mockClient),
243+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},outputDir: skillsDir, httpClient: mockClient),
244244
);
245245

246246
await IOOverrides.runZoned(() async {
@@ -274,7 +274,7 @@ void main() {
274274

275275
runner = CommandRunner('skills', 'Test runner')
276276
..addCommand(
277-
ValidateSkillCommand(outputDir: skillsDir, httpClient: mockClient),
277+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},outputDir: skillsDir, httpClient: mockClient),
278278
);
279279

280280
await runner.run(['validate-skill', configFile.path]);
@@ -319,7 +319,7 @@ void main() {
319319

320320
runner = CommandRunner('skills', 'Test runner')
321321
..addCommand(
322-
ValidateSkillCommand(
322+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
323323
outputDir: skillsDir,
324324
validationDir: validationDir,
325325
httpClient: mockClient,
@@ -368,7 +368,7 @@ void main() {
368368

369369
runner = CommandRunner('skills', 'Test runner')
370370
..addCommand(
371-
ValidateSkillCommand(
371+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
372372
outputDir: skillsDir,
373373
validationDir: validationDir,
374374
httpClient: mockClient,
@@ -415,7 +415,7 @@ void main() {
415415

416416
runner = CommandRunner('skills', 'Test runner')
417417
..addCommand(
418-
ValidateSkillCommand(
418+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
419419
outputDir: skillsDir,
420420
validationDir: validationDir,
421421
httpClient: mockClient,
@@ -494,7 +494,7 @@ Content
494494

495495
runner = CommandRunner('skills', 'Test runner')
496496
..addCommand(
497-
ValidateSkillCommand(
497+
ValidateSkillCommand(environmentOverride: {'GEMINI_API_KEY': 'fake'},
498498
outputDir: skillsDir,
499499
httpClient: mockClient,
500500
validationDir: validationDir,

0 commit comments

Comments
 (0)