Skip to content

Commit 2e8f30d

Browse files
committed
test: enhance test coverage from 3 to 14 tests
1 parent c613f3b commit 2e8f30d

File tree

1 file changed

+151
-149
lines changed

1 file changed

+151
-149
lines changed
Lines changed: 151 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,173 @@
1-
import 'package:analyzer/dart/constant/value.dart';
2-
import 'package:analyzer/dart/element/type.dart';
3-
import 'package:flutter_secure_dotenv_generator/src/annotation_generator.dart';
4-
import 'package:flutter_secure_dotenv_generator/src/environment_field.dart';
1+
import 'dart:convert';
2+
3+
import 'package:flutter_secure_dotenv/flutter_secure_dotenv.dart';
54
import 'package:flutter_secure_dotenv_generator/src/helpers.dart';
65
import 'package:test/test.dart';
7-
import 'package:analyzer/dart/element/element.dart';
8-
import 'package:build/build.dart';
9-
import 'package:source_gen/source_gen.dart';
106

117
void main() {
12-
group('FlutterSecureDotEnvAnnotationGenerator', () {
13-
late FlutterSecureDotEnvAnnotationGenerator generator;
14-
late BuilderOptions options;
15-
16-
setUp(() {
17-
options = BuilderOptions({
18-
'ENCRYPTION_KEY': 'test_encryption_key',
19-
'IV': 'test_initialization_vector',
20-
'OUTPUT_FILE': 'test_output_file',
21-
});
22-
generator = FlutterSecureDotEnvAnnotationGenerator(options);
8+
group('Helpers - escapeDartString', () {
9+
test('should escape simple string', () {
10+
final result = escapeDartString('hello');
11+
expect(result, "'hello'");
2312
});
2413

25-
test('should throw exception if element is not a class', () async {
26-
final element = FunctionElementImpl('testFunction');
27-
final annotation = ConstantReader(null);
28-
final buildStep = BuildStepMock();
29-
30-
expect(
31-
() => generator.generateForAnnotatedElement(
32-
element, annotation, buildStep),
33-
throwsException,
34-
);
14+
test('should handle string with single quotes', () {
15+
final result = escapeDartString("it's");
16+
expect(result, '"it\'s"');
3517
});
36-
});
3718

38-
group('EnvironmentField', () {
39-
test('should create an instance of EnvironmentField', () {
40-
final field = EnvironmentField('name', 'nameOverride',
41-
DartTypeImpl('String'), DartObjectImpl('defaultValue'));
42-
43-
expect(field.name, 'name');
44-
expect(field.nameOverride, 'nameOverride');
45-
expect(field.type, isA<DartType>());
46-
expect(field.defaultValue, isA<DartObject>());
19+
test('should handle string with double quotes', () {
20+
final result = escapeDartString('say "hello"');
21+
expect(result, "'say \"hello\"'");
4722
});
48-
});
49-
50-
group('Helpers', () {
51-
test('should get all accessor names', () {
52-
final interface = InterfaceElementImpl('TestInterface');
53-
final accessorNames = getAllAccessorNames(interface);
5423

55-
expect(accessorNames, isNotEmpty);
24+
test('should handle string with dollar sign', () {
25+
final result = escapeDartString(r'cost $5');
26+
expect(result, contains(r'$'));
5627
});
57-
});
58-
}
59-
60-
// ignore: subtype_of_sealed_class
61-
class BuildStepMock implements BuildStep {
62-
@override
63-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
64-
}
65-
66-
class FunctionElementImpl implements FunctionElement {
67-
@override
68-
final String name;
69-
70-
FunctionElementImpl(this.name);
71-
72-
@override
73-
List<ElementAnnotation> get metadata => [];
7428

75-
@override
76-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
77-
}
78-
79-
class ClassElementImpl implements ClassElement {
80-
@override
81-
final String name;
82-
83-
ClassElementImpl(this.name);
84-
85-
@override
86-
List<ElementAnnotation> get metadata => [];
87-
88-
@override
89-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
90-
}
91-
92-
class DartTypeImpl implements DartType {
93-
@override
94-
final String name;
95-
96-
DartTypeImpl(this.name);
97-
98-
@override
99-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
100-
}
101-
102-
class DartObjectImpl implements DartObject {
103-
final dynamic value;
104-
105-
DartObjectImpl(this.value);
106-
107-
@override
108-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
109-
}
110-
111-
class FieldElementImpl implements FieldElement {
112-
@override
113-
final String name;
114-
@override
115-
final DartType type;
116-
117-
FieldElementImpl(this.name, this.type);
118-
119-
@override
120-
List<ElementAnnotation> get metadata => [];
121-
122-
@override
123-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
124-
}
125-
126-
class InterfaceElementImpl implements InterfaceElement {
127-
@override
128-
final String name;
129-
130-
InterfaceElementImpl(this.name);
131-
132-
@override
133-
List<ElementAnnotation> get metadata => [];
134-
135-
@override
136-
List<InterfaceType> get allSupertypes => [];
29+
test('should handle string with newline', () {
30+
final result = escapeDartString('line1\nline2');
31+
expect(result, contains(r'\n'));
32+
});
13733

138-
@override
139-
List<PropertyAccessorElement> get accessors => [
140-
PropertyAccessorElementImpl('accessor1', false),
141-
PropertyAccessorElementImpl('accessor2', false),
142-
];
34+
test('should handle string with tab', () {
35+
final result = escapeDartString('col1\tcol2');
36+
expect(result, contains(r'\t'));
37+
});
14338

144-
@override
145-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
146-
}
39+
test('should handle string with carriage return', () {
40+
final result = escapeDartString('line1\rline2');
41+
expect(result, contains(r'\r'));
42+
});
14743

148-
class LibraryElementImpl implements LibraryElement {
149-
@override
150-
final String name;
44+
test('should handle empty string', () {
45+
final result = escapeDartString('');
46+
expect(result, "''");
47+
});
15148

152-
LibraryElementImpl(this.name);
49+
test('should handle backslash', () {
50+
final result = escapeDartString(r'path\to\file');
51+
expect(result, contains(r'\\'));
52+
});
53+
});
15354

154-
@override
155-
List<ElementAnnotation> get metadata => [];
55+
group('AES encryption integration (used by generator)', () {
56+
test('encrypt and decrypt JSON map (simulates generator workflow)', () {
57+
final key = AESCBCEncrypter.generateRandomBytes(32);
58+
final iv = AESCBCEncrypter.generateRandomBytes(16);
59+
60+
final envMap = {
61+
'API_KEY': 'sk-1234567890abcdef',
62+
'DATABASE_URL': 'postgres://user:pass@localhost:5432/db',
63+
'DEBUG': 'true',
64+
'PORT': '3000',
65+
};
66+
67+
// Encrypt individual values (as the generator does)
68+
final encryptedEntries = <String, String>{};
69+
for (final entry in envMap.entries) {
70+
final encrypted = base64.encode(
71+
AESCBCEncrypter.aesCbcEncrypt(key, iv, entry.value),
72+
);
73+
encryptedEntries[entry.key] = encrypted;
74+
}
75+
76+
// Encrypt the whole JSON map (as the generator does)
77+
final jsonEncoded = jsonEncode(encryptedEntries);
78+
final encryptedJson = AESCBCEncrypter.aesCbcEncrypt(key, iv, jsonEncoded);
79+
80+
// Decrypt the whole JSON map (as the runtime does)
81+
final decryptedJson = AESCBCEncrypter.aesCbcDecrypt(
82+
key,
83+
iv,
84+
encryptedJson,
85+
);
86+
final jsonMap = jsonDecode(decryptedJson) as Map<String, dynamic>;
87+
88+
// Decrypt individual values
89+
for (final entry in envMap.entries) {
90+
final encryptedValue = jsonMap[entry.key] as String;
91+
final decryptedValue = AESCBCEncrypter.aesCbcDecrypt(
92+
key,
93+
iv,
94+
base64.decode(encryptedValue),
95+
);
96+
expect(decryptedValue, entry.value);
97+
}
98+
});
15699

157-
@override
158-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
159-
}
100+
test('base64 key/IV round-trip (simulates key file workflow)', () {
101+
final key = AESCBCEncrypter.generateRandomBytes(32);
102+
final iv = AESCBCEncrypter.generateRandomBytes(16);
103+
104+
// Serialize to JSON file format
105+
final secretsMap = {
106+
'ENCRYPTION_KEY': base64.encode(key),
107+
'IV': base64.encode(iv),
108+
};
109+
final jsonStr = jsonEncode(secretsMap);
110+
111+
// Deserialize
112+
final loaded = jsonDecode(jsonStr) as Map<String, dynamic>;
113+
final loadedKey = base64.decode(loaded['ENCRYPTION_KEY']! as String);
114+
final loadedIv = base64.decode(loaded['IV']! as String);
115+
116+
expect(Uint8List.fromList(loadedKey), equals(key));
117+
expect(Uint8List.fromList(loadedIv), equals(iv));
118+
119+
// Verify encryption still works with deserialized keys
120+
const text = 'secret value';
121+
final encrypted = AESCBCEncrypter.aesCbcEncrypt(
122+
Uint8List.fromList(loadedKey),
123+
Uint8List.fromList(loadedIv),
124+
text,
125+
);
126+
final decrypted = AESCBCEncrypter.aesCbcDecrypt(
127+
Uint8List.fromList(loadedKey),
128+
Uint8List.fromList(loadedIv),
129+
encrypted,
130+
);
131+
expect(decrypted, text);
132+
});
160133

161-
class PropertyAccessorElementImpl implements PropertyAccessorElement {
162-
@override
163-
final String name;
164-
@override
165-
final bool isSetter;
134+
test('unencrypted base64 workflow (no encryption keys)', () {
135+
final envMap = {'API_KEY': 'my-api-key', 'SECRET': 'my-secret'};
136+
137+
// Simulate unencrypted path: base64-encode values
138+
final encodedEntries = <String, String>{};
139+
for (final entry in envMap.entries) {
140+
encodedEntries[entry.key] = base64.encode(entry.value.codeUnits);
141+
}
142+
143+
final jsonEncoded = jsonEncode(encodedEntries);
144+
final encryptedJson = base64.encode(jsonEncoded.codeUnits);
145+
146+
// Decode
147+
final bytes = base64.decode(encryptedJson);
148+
final stringDecoded = String.fromCharCodes(bytes);
149+
final jsonMap = jsonDecode(stringDecoded) as Map<String, dynamic>;
150+
151+
for (final entry in envMap.entries) {
152+
final encryptedValue = jsonMap[entry.key] as String;
153+
final decryptedValue = String.fromCharCodes(
154+
base64.decode(encryptedValue),
155+
);
156+
expect(decryptedValue, entry.value);
157+
}
158+
});
159+
});
166160

167-
PropertyAccessorElementImpl(this.name, this.isSetter);
161+
group('FieldRename integration', () {
162+
test('all FieldRename values accessible', () {
163+
expect(FieldRename.values, hasLength(5));
164+
});
168165

169-
@override
170-
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
166+
test('DotEnvGen accepts all FieldRename values', () {
167+
for (final rename in FieldRename.values) {
168+
final gen = DotEnvGen(fieldRename: rename);
169+
expect(gen.fieldRename, rename);
170+
}
171+
});
172+
});
171173
}

0 commit comments

Comments
 (0)