sf-forcekit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/License +21 -0
  2. package/README.md +106 -0
  3. package/bin/cli.js +85 -0
  4. package/package.json +44 -0
  5. package/templates/ai-dir/README.md +155 -0
  6. package/templates/ai-dir/agentforce.md +213 -0
  7. package/templates/ai-dir/architecture.md +123 -0
  8. package/templates/ai-dir/commands.md +276 -0
  9. package/templates/ai-dir/context-snapshots/TEMPLATE.md +64 -0
  10. package/templates/ai-dir/conventions.md +242 -0
  11. package/templates/ai-dir/current-state.md +113 -0
  12. package/templates/ai-dir/debugging-notes.md +165 -0
  13. package/templates/ai-dir/deployment.md +161 -0
  14. package/templates/ai-dir/integrations.md +199 -0
  15. package/templates/ai-dir/inventory.md +209 -0
  16. package/templates/ai-dir/known-issues.md +124 -0
  17. package/templates/ai-dir/org-context.md +110 -0
  18. package/templates/ai-dir/performance.md +312 -0
  19. package/templates/ai-dir/prompts/agentforce.md +163 -0
  20. package/templates/ai-dir/prompts/apex.md +165 -0
  21. package/templates/ai-dir/prompts/flows.md +125 -0
  22. package/templates/ai-dir/prompts/lwc.md +230 -0
  23. package/templates/ai-dir/prompts/security.md +181 -0
  24. package/templates/ai-dir/prompts/testing.md +269 -0
  25. package/templates/ai-dir/rules.md +238 -0
  26. package/templates/ai-dir/scripts/update_state.py +1406 -0
  27. package/templates/ai-dir/source-of-truth.md +180 -0
  28. package/templates/ai-dir/templates/Selector.cls +113 -0
  29. package/templates/ai-dir/templates/Service.cls +132 -0
  30. package/templates/ai-dir/templates/TestClass.cls +143 -0
  31. package/templates/ai-dir/templates/TriggerHandler.cls +67 -0
  32. package/templates/ai-dir/testing-strategy.md +342 -0
@@ -0,0 +1,342 @@
1
+ # Testing Strategy
2
+
3
+ > How we test, what we test, and why. Agents MUST follow these patterns.
4
+
5
+ ---
6
+
7
+ ## Testing Philosophy
8
+
9
+ 1. **Test behavior, not implementation** — Assert what the code *does*, not how it does it.
10
+ 2. **Bulk by default** — Every test uses 200+ records unless explicitly testing a single-record path.
11
+ 3. **Negative paths are mandatory** — If a method can fail, test the failure.
12
+ 4. **Permissions matter** — Test with restricted users, not just System Administrator.
13
+ 5. **No org data** — `SeeAllData=true` is permanently banned.
14
+
15
+ ---
16
+
17
+ ## Coverage Requirements
18
+
19
+ | Target | Minimum | Standard |
20
+ |--------|---------|----------|
21
+ | Per class | 75% | **90%+** |
22
+ | Org-wide | 75% | **85%+** |
23
+ | New code (PRs) | 90% | **95%+** |
24
+
25
+ ---
26
+
27
+ ## Test Categories
28
+
29
+ ### Unit Tests (Required for every class)
30
+
31
+ Tests a single method in isolation. Mock all dependencies.
32
+
33
+ ```apex
34
+ @IsTest
35
+ private class AccountServiceTest {
36
+
37
+ @TestSetup
38
+ static void setupTestData() {
39
+ // Use TestDataFactory — never hardcode test data inline
40
+ List<Account> accounts = TestDataFactory.createAccounts(200);
41
+ insert accounts;
42
+ }
43
+
44
+ @IsTest
45
+ static void testProcessAccounts_BulkPositive() {
46
+ // Arrange
47
+ List<Account> accounts = [SELECT Id, Name FROM Account];
48
+ System.assertEquals(200, accounts.size(), 'Setup should create 200 accounts');
49
+
50
+ // Act
51
+ Test.startTest();
52
+ AccountService.processAccounts(accounts);
53
+ Test.stopTest();
54
+
55
+ // Assert
56
+ List<Account> updated = [SELECT Id, Status__c FROM Account WHERE Id IN :accounts];
57
+ for (Account a : updated) {
58
+ System.assertNotEquals(null, a.Status__c, 'Status should be set after processing');
59
+ }
60
+ }
61
+
62
+ @IsTest
63
+ static void testProcessAccounts_EmptyList() {
64
+ // Negative: empty input should not throw
65
+ Test.startTest();
66
+ AccountService.processAccounts(new List<Account>());
67
+ Test.stopTest();
68
+ // No exception = pass
69
+ }
70
+
71
+ @IsTest
72
+ static void testProcessAccounts_NullInput() {
73
+ // Negative: null input should throw
74
+ try {
75
+ AccountService.processAccounts(null);
76
+ System.assert(false, 'Should have thrown exception for null input');
77
+ } catch (AccountServiceException e) {
78
+ System.assert(e.getMessage().contains('cannot be null'),
79
+ 'Exception message should mention null: ' + e.getMessage());
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### Integration Tests (Required for callouts & triggers)
86
+
87
+ Tests end-to-end flow including DML and trigger execution.
88
+
89
+ ```apex
90
+ @IsTest
91
+ private class AccountTriggerTest {
92
+
93
+ @TestSetup
94
+ static void setupTestData() {
95
+ List<Account> accounts = TestDataFactory.createAccounts(200);
96
+ insert accounts;
97
+ }
98
+
99
+ @IsTest
100
+ static void testAfterUpdate_StatusChange_FiresRelatedUpdate() {
101
+ // Arrange
102
+ List<Account> accounts = [SELECT Id, Status__c FROM Account];
103
+ for (Account a : accounts) {
104
+ a.Status__c = 'Active';
105
+ }
106
+
107
+ // Act — trigger fires on update
108
+ Test.startTest();
109
+ update accounts;
110
+ Test.stopTest();
111
+
112
+ // Assert — check related records were updated
113
+ List<Contact> contacts = [SELECT Id, Account_Status__c FROM Contact WHERE AccountId IN :accounts];
114
+ for (Contact c : contacts) {
115
+ System.assertEquals('Active', c.Account_Status__c,
116
+ 'Contact should reflect parent Account status');
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### Callout Mock Tests (Required for all integrations)
123
+
124
+ ```apex
125
+ @IsTest
126
+ private class ExternalApiServiceTest {
127
+
128
+ private class MockSuccess implements HttpCalloutMock {
129
+ public HttpResponse respond(HttpRequest req) {
130
+ HttpResponse res = new HttpResponse();
131
+ res.setStatusCode(200);
132
+ res.setBody('{"status":"success","id":"ext-123"}');
133
+ res.setHeader('Content-Type', 'application/json');
134
+ return res;
135
+ }
136
+ }
137
+
138
+ private class MockFailure implements HttpCalloutMock {
139
+ public HttpResponse respond(HttpRequest req) {
140
+ HttpResponse res = new HttpResponse();
141
+ res.setStatusCode(500);
142
+ res.setBody('{"error":"Internal Server Error"}');
143
+ return res;
144
+ }
145
+ }
146
+
147
+ @IsTest
148
+ static void testCallExternalApi_Success() {
149
+ Test.setMock(HttpCalloutMock.class, new MockSuccess());
150
+
151
+ Test.startTest();
152
+ ApiResponseDTO result = ExternalApiService.callExternalApi('/orders', 'GET', null);
153
+ Test.stopTest();
154
+
155
+ System.assertEquals('success', result.status, 'Should parse success response');
156
+ }
157
+
158
+ @IsTest
159
+ static void testCallExternalApi_ServerError() {
160
+ Test.setMock(HttpCalloutMock.class, new MockFailure());
161
+
162
+ Test.startTest();
163
+ try {
164
+ ExternalApiService.callExternalApi('/orders', 'GET', null);
165
+ System.assert(false, 'Should throw IntegrationException on 500');
166
+ } catch (IntegrationException e) {
167
+ System.assert(e.getMessage().contains('500'),
168
+ 'Exception should contain status code');
169
+ }
170
+ Test.stopTest();
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### Permission Tests (Required for customer-facing features)
176
+
177
+ ```apex
178
+ @IsTest
179
+ private class AccountService_PermissionTest {
180
+
181
+ @IsTest
182
+ static void testReadAccess_RestrictedUser() {
183
+ // Create a user with minimal permissions
184
+ User restrictedUser = TestDataFactory.createUser('Standard User');
185
+
186
+ System.runAs(restrictedUser) {
187
+ Test.startTest();
188
+ try {
189
+ List<Account> results = AccountService.getAccounts(new Set<Id>());
190
+ // Should return empty, not throw
191
+ System.assertEquals(0, results.size(), 'Restricted user should see no accounts');
192
+ } catch (Exception e) {
193
+ System.assert(false,
194
+ 'Should not throw for restricted user, should return empty: ' + e.getMessage());
195
+ }
196
+ Test.stopTest();
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## TestDataFactory Pattern
205
+
206
+ Every project MUST have a `TestDataFactory` class. Never create test data inline.
207
+
208
+ ```apex
209
+ @IsTest
210
+ public class TestDataFactory {
211
+
212
+ public static List<Account> createAccounts(Integer count) {
213
+ List<Account> accounts = new List<Account>();
214
+ for (Integer i = 0; i < count; i++) {
215
+ accounts.add(new Account(
216
+ Name = 'Test Account ' + i,
217
+ Industry = 'Technology'
218
+ ));
219
+ }
220
+ return accounts;
221
+ }
222
+
223
+ public static List<Contact> createContacts(Integer count, Id accountId) {
224
+ List<Contact> contacts = new List<Contact>();
225
+ for (Integer i = 0; i < count; i++) {
226
+ contacts.add(new Contact(
227
+ FirstName = 'Test',
228
+ LastName = 'Contact ' + i,
229
+ AccountId = accountId,
230
+ Email = 'test' + i + '@example.com'
231
+ ));
232
+ }
233
+ return contacts;
234
+ }
235
+
236
+ public static User createUser(String profileName) {
237
+ Profile p = [SELECT Id FROM Profile WHERE Name = :profileName LIMIT 1];
238
+ return new User(
239
+ FirstName = 'Test',
240
+ LastName = 'User',
241
+ Email = 'testuser@example.com',
242
+ Username = 'testuser' + DateTime.now().getTime() + '@example.com',
243
+ Alias = 'tuser',
244
+ ProfileId = p.Id,
245
+ TimeZoneSidKey = 'America/New_York',
246
+ LocaleSidKey = 'en_US',
247
+ EmailEncodingKey = 'UTF-8',
248
+ LanguageLocaleKey = 'en_US'
249
+ );
250
+ }
251
+ }
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Test Naming Convention
257
+
258
+ ```
259
+ {MethodName}_{Scenario}_{ExpectedResult}
260
+ ```
261
+
262
+ | Example | Meaning |
263
+ |---------|---------|
264
+ | `testGetAccounts_ValidIds_ReturnsAccounts` | Happy path |
265
+ | `testGetAccounts_EmptySet_ReturnsEmpty` | Empty input |
266
+ | `testGetAccounts_NullInput_ThrowsException` | Null input |
267
+ | `testGetAccounts_200Records_NoLimitErrors` | Bulk test |
268
+ | `testGetAccounts_RestrictedUser_ReturnsEmpty` | Permission |
269
+
270
+ ---
271
+
272
+ ## Governor Limit Testing
273
+
274
+ ```apex
275
+ @IsTest
276
+ static void testBulkProcessing_200Records_NoGovernorViolations() {
277
+ List<Account> accounts = TestDataFactory.createAccounts(200);
278
+ insert accounts;
279
+
280
+ Test.startTest();
281
+
282
+ Integer queriesBefore = Limits.getQueries();
283
+ Integer dmlBefore = Limits.getDmlStatements();
284
+
285
+ AccountService.processAccounts(accounts);
286
+
287
+ Integer queriesUsed = Limits.getQueries() - queriesBefore;
288
+ Integer dmlUsed = Limits.getDmlStatements() - dmlBefore;
289
+
290
+ Test.stopTest();
291
+
292
+ // Assert governor-friendly behavior
293
+ System.assert(queriesUsed <= 5,
294
+ 'Should use ≤5 SOQL queries for 200 records, used: ' + queriesUsed);
295
+ System.assert(dmlUsed <= 3,
296
+ 'Should use ≤3 DML statements for 200 records, used: ' + dmlUsed);
297
+ }
298
+ ```
299
+
300
+ ---
301
+
302
+ ## Test Execution Commands
303
+
304
+ ```bash
305
+ # Run all local tests
306
+ sf apex run test --target-org <target_org> --code-coverage --result-format human --test-level RunLocalTests
307
+
308
+ # Run one test class
309
+ sf apex run test --target-org <target_org> --tests AccountServiceTest --code-coverage --result-format human
310
+
311
+ # Run one test method
312
+ sf apex run test --target-org <target_org> --tests "AccountServiceTest.testBulkInsert" --code-coverage --result-format human
313
+
314
+ # Run tests with JSON output (for CI/CD parsing)
315
+ sf apex run test --target-org <target_org> --code-coverage --result-format json --output-dir test-results --json
316
+ ```
317
+
318
+ ---
319
+
320
+ ## What NOT to Test
321
+
322
+ | Don't Test | Why |
323
+ |-----------|-----|
324
+ | Standard Salesforce behavior (e.g., required field validation) | Platform handles it |
325
+ | Getter/setter methods with no logic | Trivial, adds no value |
326
+ | Private methods directly | Test through public interface |
327
+ | Platform UI behavior | Not testable via Apex |
328
+
329
+ ---
330
+
331
+ ## Test Review Checklist
332
+
333
+ - [ ] Uses `@TestSetup` for shared data
334
+ - [ ] `SeeAllData=true` is NOT used
335
+ - [ ] Tests 200+ records (bulk)
336
+ - [ ] Tests negative paths (null, empty, invalid)
337
+ - [ ] Tests permission scenarios (`System.runAs`)
338
+ - [ ] Tests callout mocks (`HttpCalloutMock`)
339
+ - [ ] Every assertion has a meaningful message
340
+ - [ ] `Test.startTest()` / `Test.stopTest()` bracket the action
341
+ - [ ] No hardcoded IDs in test data
342
+ - [ ] Coverage ≥ 90%