backend-manager 5.11.5 → 5.11.7
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.
- package/package.json +1 -1
- package/src/cli/commands/serve.js +49 -2
- package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +4 -1
- package/src/manager/libraries/prompts/infer-contact.md +5 -0
- package/src/manager/routes/marketing/contact/post.js +4 -1
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-24-21/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-24-21/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-24-21/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-24-21/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-24-21/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-24-21/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-25-51/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-25-51/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-25-51/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-25-51/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-25-51/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-25-51/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-27-47/metadata.json +14 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-27-47/newsletter.html +486 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-27-47/newsletter.md +41 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-27-47/newsletter.mjml +97 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-27-47/structure.json +42 -0
- package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-07-03T23-27-47/summary.md +1 -0
- package/src/test/fixtures/firebase-project/.temp/test-mode.json +1 -1
- package/src/test/fixtures/firebase-project/database-debug.log +8 -8
- package/src/test/fixtures/firebase-project/firestore-debug.log +56 -54
- package/src/test/fixtures/firebase-project/pubsub-debug.log +4 -4
- package/test/email/marketing-lifecycle.js +5 -0
- package/test/email/validation.js +42 -7
- package/test/functions/admin/edit-post.js +14 -10
- package/test/functions/general/add-marketing-contact.js +56 -16
- package/test/routes/admin/post.js +14 -10
- package/test/routes/marketing/contact.js +43 -10
|
@@ -2,11 +2,11 @@ WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
|
|
2
2
|
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by akka.util.Unsafe (file:/Users/ian/.cache/firebase/emulators/firebase-database-emulator-v4.11.2.jar)
|
|
3
3
|
WARNING: Please consider reporting this to the maintainers of class akka.util.Unsafe
|
|
4
4
|
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
17:56:43.904 [NamespaceSystem-akka.actor.default-dispatcher-4] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
|
|
6
|
+
17:56:44.025 [main] INFO com.firebase.server.forge.App$ - Listening at 127.0.0.1:9000
|
|
7
|
+
17:56:55.055 [NamespaceSystem-akka.actor.default-dispatcher-4] INFO com.firebase.core.namespace.NamespaceActor - demo-backend-manager-default-rtdb successfully activated FBKV (SurveyIdle(0)) wait: 84ms, init: 0ms
|
|
8
|
+
17:56:55.099 [NamespaceSystem-blocking-namespace-operation-dispatcher-6] INFO com.firebase.core.namespace.StateManager - Namespace demo-backend-manager-default-rtdb status Active to Active
|
|
9
|
+
17:57:09.904 [Thread-0] INFO com.firebase.server.forge.App$ - Attempting graceful shutdown.
|
|
10
|
+
17:57:09.909 [NamespaceSystem-akka.actor.default-dispatcher-4] INFO com.firebase.core.namespace.Terminator$Terminator - 1 actors left to terminate: demo-backend-manager-default-rtdb
|
|
11
|
+
17:57:09.913 [NamespaceSystem-akka.actor.default-dispatcher-4] INFO com.firebase.core.namespace.NamespaceActor - stopped namespace actor for demo-backend-manager-default-rtdb
|
|
12
|
+
17:57:09.917 [Thread-0] INFO com.firebase.server.forge.App$ - Graceful shutdown complete.
|
|
@@ -2,7 +2,7 @@ WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
|
|
2
2
|
WARNING: sun.misc.Unsafe::allocateMemory has been called by io.netty.util.internal.PlatformDependent0$2 (file:/Users/ian/.cache/firebase/emulators/cloud-firestore-emulator-v1.21.0.jar)
|
|
3
3
|
WARNING: Please consider reporting this to the maintainers of class io.netty.util.internal.PlatformDependent0$2
|
|
4
4
|
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
|
|
5
|
-
Jul
|
|
5
|
+
Jul 03, 2026 5:56:42 PM com.google.cloud.datastore.emulator.firestore.websocket.WebSocketServer start
|
|
6
6
|
INFO: Started WebSocket server on ws://127.0.0.1:9150
|
|
7
7
|
|
|
8
8
|
API endpoint: http://127.0.0.1:8080
|
|
@@ -20,111 +20,113 @@ If you are running a Firestore in Datastore Mode project, run:
|
|
|
20
20
|
Note: Support for Datastore Mode is in preview. If you encounter any bugs please file at https://github.com/firebase/firebase-tools/issues.
|
|
21
21
|
Dev App Server is now running.
|
|
22
22
|
|
|
23
|
-
Jul
|
|
23
|
+
Jul 03, 2026 5:56:54 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
24
24
|
INFO: Detected non-HTTP/2 connection.
|
|
25
|
-
Jul
|
|
25
|
+
Jul 03, 2026 5:56:54 PM io.grpc.netty.TcpMetrics loadEpollInfo
|
|
26
26
|
INFO: Epoll available during static init of TcpMetrics:false
|
|
27
|
-
Jul
|
|
27
|
+
Jul 03, 2026 5:56:54 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
28
28
|
INFO: Detected HTTP/2 connection.
|
|
29
|
-
Jul
|
|
29
|
+
Jul 03, 2026 5:56:56 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
30
30
|
INFO: Detected HTTP/2 connection.
|
|
31
|
-
Jul
|
|
31
|
+
Jul 03, 2026 5:56:56 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
32
32
|
INFO: Detected HTTP/2 connection.
|
|
33
|
-
Jul
|
|
33
|
+
Jul 03, 2026 5:57:07 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
34
34
|
INFO: Detected HTTP/2 connection.
|
|
35
|
-
Jul
|
|
35
|
+
Jul 03, 2026 5:57:07 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
36
36
|
INFO: Detected HTTP/2 connection.
|
|
37
|
-
Jul
|
|
37
|
+
Jul 03, 2026 5:57:07 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
38
38
|
INFO: Detected HTTP/2 connection.
|
|
39
|
-
Jul
|
|
39
|
+
Jul 03, 2026 5:57:07 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
40
40
|
INFO: Detected HTTP/2 connection.
|
|
41
|
-
Jul
|
|
41
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
42
42
|
INFO: Detected HTTP/2 connection.
|
|
43
|
-
Jul
|
|
43
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
44
44
|
INFO: Detected HTTP/2 connection.
|
|
45
|
-
Jul
|
|
45
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
46
46
|
INFO: Detected HTTP/2 connection.
|
|
47
|
-
Jul
|
|
47
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
48
48
|
INFO: Detected HTTP/2 connection.
|
|
49
|
-
Jul
|
|
49
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
50
50
|
INFO: Detected HTTP/2 connection.
|
|
51
|
-
Jul
|
|
51
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
52
52
|
INFO: Detected HTTP/2 connection.
|
|
53
|
-
Jul
|
|
53
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
54
54
|
INFO: Detected HTTP/2 connection.
|
|
55
|
-
Jul
|
|
55
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
56
56
|
INFO: Detected HTTP/2 connection.
|
|
57
|
-
Jul
|
|
57
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
58
58
|
INFO: Detected HTTP/2 connection.
|
|
59
|
-
Jul
|
|
59
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
60
60
|
INFO: Detected HTTP/2 connection.
|
|
61
|
-
Jul
|
|
61
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
62
62
|
INFO: Detected HTTP/2 connection.
|
|
63
|
-
Jul
|
|
63
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
64
64
|
INFO: Detected HTTP/2 connection.
|
|
65
|
-
Jul
|
|
65
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
66
66
|
INFO: Detected HTTP/2 connection.
|
|
67
|
-
Jul
|
|
67
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
68
68
|
INFO: Detected HTTP/2 connection.
|
|
69
|
-
Jul
|
|
69
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
70
70
|
INFO: Detected HTTP/2 connection.
|
|
71
|
-
Jul
|
|
71
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
72
72
|
INFO: Detected HTTP/2 connection.
|
|
73
|
-
Jul
|
|
73
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
74
74
|
INFO: Detected HTTP/2 connection.
|
|
75
|
-
Jul
|
|
75
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
76
76
|
INFO: Detected HTTP/2 connection.
|
|
77
|
-
Jul
|
|
77
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
78
78
|
INFO: Detected HTTP/2 connection.
|
|
79
|
-
Jul
|
|
79
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
80
80
|
INFO: Detected HTTP/2 connection.
|
|
81
|
-
Jul
|
|
81
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
82
82
|
INFO: Detected HTTP/2 connection.
|
|
83
|
-
Jul
|
|
83
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
84
84
|
INFO: Detected HTTP/2 connection.
|
|
85
|
-
Jul
|
|
85
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
86
86
|
INFO: Detected HTTP/2 connection.
|
|
87
|
-
Jul
|
|
87
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
88
88
|
INFO: Detected HTTP/2 connection.
|
|
89
|
-
Jul
|
|
89
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
90
90
|
INFO: Detected HTTP/2 connection.
|
|
91
|
-
Jul
|
|
91
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
92
92
|
INFO: Detected HTTP/2 connection.
|
|
93
|
-
Jul
|
|
93
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
94
94
|
INFO: Detected HTTP/2 connection.
|
|
95
|
-
Jul
|
|
95
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
96
96
|
INFO: Detected HTTP/2 connection.
|
|
97
|
-
Jul
|
|
97
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
98
98
|
INFO: Detected HTTP/2 connection.
|
|
99
|
-
Jul
|
|
99
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
100
100
|
INFO: Detected HTTP/2 connection.
|
|
101
|
-
Jul
|
|
101
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
102
102
|
INFO: Detected HTTP/2 connection.
|
|
103
|
-
Jul
|
|
103
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
104
104
|
INFO: Detected HTTP/2 connection.
|
|
105
|
-
Jul
|
|
105
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
106
106
|
INFO: Detected HTTP/2 connection.
|
|
107
|
-
Jul
|
|
107
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
108
108
|
INFO: Detected HTTP/2 connection.
|
|
109
|
-
Jul
|
|
109
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
110
110
|
INFO: Detected HTTP/2 connection.
|
|
111
|
-
Jul
|
|
111
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
112
112
|
INFO: Detected HTTP/2 connection.
|
|
113
|
-
Jul
|
|
113
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
114
114
|
INFO: Detected HTTP/2 connection.
|
|
115
|
-
Jul
|
|
115
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
116
116
|
INFO: Detected HTTP/2 connection.
|
|
117
|
-
Jul
|
|
117
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
118
118
|
INFO: Detected HTTP/2 connection.
|
|
119
|
-
Jul
|
|
119
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
120
120
|
INFO: Detected HTTP/2 connection.
|
|
121
|
-
Jul
|
|
121
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
122
122
|
INFO: Detected HTTP/2 connection.
|
|
123
|
-
Jul
|
|
123
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
124
124
|
INFO: Detected HTTP/2 connection.
|
|
125
|
-
Jul
|
|
125
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
126
126
|
INFO: Detected HTTP/2 connection.
|
|
127
|
-
Jul
|
|
127
|
+
Jul 03, 2026 5:57:08 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
128
|
+
INFO: Detected HTTP/2 connection.
|
|
129
|
+
Jul 03, 2026 5:57:09 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
128
130
|
INFO: Detected HTTP/2 connection.
|
|
129
131
|
*** shutting down gRPC server since JVM is shutting down
|
|
130
132
|
*** server shut down
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
This is the Google Pub/Sub fake.
|
|
2
2
|
Implementation may be incomplete or differ from the real system.
|
|
3
|
-
Jul
|
|
3
|
+
Jul 03, 2026 5:56:47 PM com.google.cloud.pubsub.testing.v1.Main main
|
|
4
4
|
INFO: IAM integration is disabled. IAM policy methods and ACL checks are not supported
|
|
5
5
|
SLF4J(W): No SLF4J providers were found.
|
|
6
6
|
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
|
|
7
7
|
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
|
|
8
8
|
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
|
|
9
|
-
WARNING: sun.misc.Unsafe::allocateMemory has been called by io.netty.util.internal.PlatformDependent0$2 (file:/Users/ian/.cache/firebase/emulators/pubsub-emulator-0.8.
|
|
9
|
+
WARNING: sun.misc.Unsafe::allocateMemory has been called by io.netty.util.internal.PlatformDependent0$2 (file:/Users/ian/.cache/firebase/emulators/pubsub-emulator-0.8.34/pubsub-emulator/lib/cloud-pubsub-emulator-0.8.34-all.jar)
|
|
10
10
|
WARNING: Please consider reporting this to the maintainers of class io.netty.util.internal.PlatformDependent0$2
|
|
11
11
|
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release
|
|
12
|
-
Jul
|
|
12
|
+
Jul 03, 2026 5:56:48 PM com.google.cloud.pubsub.testing.v1.Main main
|
|
13
13
|
INFO: Server started, listening on 8085
|
|
14
|
-
Jul
|
|
14
|
+
Jul 03, 2026 5:56:54 PM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead
|
|
15
15
|
INFO: Detected HTTP/2 connection.
|
|
16
16
|
*** shutting down gRPC server since JVM is shutting down
|
|
17
17
|
*** server shut down
|
|
@@ -37,6 +37,11 @@ module.exports = {
|
|
|
37
37
|
firstName: 'Lifecycle',
|
|
38
38
|
lastName: 'Test',
|
|
39
39
|
source: 'bem-test-lifecycle',
|
|
40
|
+
// skipValidation bypasses the mailbox verification check — the lifecycle
|
|
41
|
+
// address has no real mailbox so the provider (correctly) marks it
|
|
42
|
+
// undeliverable. This suite tests add → sync → remove against the real
|
|
43
|
+
// providers, not deliverability (covered by email/validation + contact tests).
|
|
44
|
+
skipValidation: true,
|
|
40
45
|
});
|
|
41
46
|
|
|
42
47
|
assert.isSuccess(response, 'Add contact should succeed');
|
package/test/email/validation.js
CHANGED
|
@@ -679,8 +679,10 @@ module.exports = {
|
|
|
679
679
|
async run({ assert, skip }) {
|
|
680
680
|
const result = await validate('disposable@gmail.com', { checks: ALL_CHECKS });
|
|
681
681
|
|
|
682
|
-
|
|
683
|
-
|
|
682
|
+
// Provider errors fail open as { valid: true, error } with NO status field —
|
|
683
|
+
// out of credits, bad key, timeouts. Nothing assertable; skip.
|
|
684
|
+
if (result.checks.mailbox?.error) {
|
|
685
|
+
skip(`Mailbox verification unavailable: ${result.checks.mailbox.error}`);
|
|
684
686
|
}
|
|
685
687
|
|
|
686
688
|
assert.hasProperty(result, 'checks.mailbox', 'Should have mailbox check');
|
|
@@ -689,7 +691,7 @@ module.exports = {
|
|
|
689
691
|
},
|
|
690
692
|
|
|
691
693
|
{
|
|
692
|
-
name: 'mailbox-
|
|
694
|
+
name: 'mailbox-not-called-when-dns-fails',
|
|
693
695
|
timeout: 15000,
|
|
694
696
|
skip: !process.env.TEST_EXTENDED_MODE || !HAS_MAILBOX_API_KEY
|
|
695
697
|
? 'TEST_EXTENDED_MODE or mailbox API key not set'
|
|
@@ -698,12 +700,45 @@ module.exports = {
|
|
|
698
700
|
async run({ assert, skip }) {
|
|
699
701
|
const result = await validate('rachel.greene@thisfakedomain99999.com', { checks: ALL_CHECKS });
|
|
700
702
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
+
// Transient resolver errors fail open (dns: { valid: true, skipped: true })
|
|
704
|
+
// and let mailbox run — the short-circuit can't be observed that run.
|
|
705
|
+
if (result.checks.dns?.skipped) {
|
|
706
|
+
skip(`DNS lookup errored (${result.checks.dns.reason}) — cannot observe short-circuit`);
|
|
703
707
|
}
|
|
704
708
|
|
|
705
|
-
assert.
|
|
706
|
-
assert.
|
|
709
|
+
assert.equal(result.valid, false, 'Fake domain should be invalid');
|
|
710
|
+
assert.propertyEquals(result, 'checks.dns.valid', false, 'DNS check should fail (NXDOMAIN)');
|
|
711
|
+
assert.equal(
|
|
712
|
+
result.checks.mailbox,
|
|
713
|
+
undefined,
|
|
714
|
+
'Mailbox must NOT run after dns fails — the short-circuit saves a paid API call'
|
|
715
|
+
);
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
|
|
719
|
+
{
|
|
720
|
+
name: 'mailbox-nonexistent-mailbox-not-valid',
|
|
721
|
+
timeout: 15000,
|
|
722
|
+
skip: !process.env.TEST_EXTENDED_MODE || !HAS_MAILBOX_API_KEY
|
|
723
|
+
? 'TEST_EXTENDED_MODE or mailbox API key not set'
|
|
724
|
+
: false,
|
|
725
|
+
|
|
726
|
+
async run({ assert, skip }) {
|
|
727
|
+
// Real-MX domain (gmail passes dns) with a fabricated local part — this is
|
|
728
|
+
// the case that actually reaches the paid mailbox provider.
|
|
729
|
+
const result = await validate('rachel.greene.zk84qx@gmail.com', { checks: ALL_CHECKS });
|
|
730
|
+
|
|
731
|
+
assert.hasProperty(result, 'checks.mailbox', 'Should have mailbox check');
|
|
732
|
+
|
|
733
|
+
const mb = result.checks.mailbox;
|
|
734
|
+
|
|
735
|
+
// Provider errors fail open as { valid: true, error } with NO status; skip.
|
|
736
|
+
if (mb.error) {
|
|
737
|
+
skip(`Mailbox verification unavailable: ${mb.error}`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
assert.hasProperty(mb, 'status', 'Should have status');
|
|
741
|
+
assert.notEqual(mb.status, 'valid', 'Fabricated gmail mailbox should not verify as valid');
|
|
707
742
|
},
|
|
708
743
|
},
|
|
709
744
|
],
|
|
@@ -243,10 +243,10 @@ module.exports = {
|
|
|
243
243
|
{
|
|
244
244
|
name: 'verify-edit',
|
|
245
245
|
auth: 'admin',
|
|
246
|
-
timeout:
|
|
246
|
+
timeout: 90000, // waitFor polls up to 60s for GitHub content propagation
|
|
247
247
|
skip: !process.env.GH_TOKEN ? 'GH_TOKEN env var not set' : false,
|
|
248
248
|
|
|
249
|
-
async run({ assert, state }) {
|
|
249
|
+
async run({ assert, state, waitFor }) {
|
|
250
250
|
if (!state.postPath || state.editSkipped) {
|
|
251
251
|
assert.ok(true, 'Skipping verify - edit was not performed');
|
|
252
252
|
return;
|
|
@@ -254,15 +254,19 @@ module.exports = {
|
|
|
254
254
|
|
|
255
255
|
const octokit = new Octokit({ auth: process.env.GH_TOKEN });
|
|
256
256
|
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
// GitHub's contents API is eventually consistent after createOrUpdateFileContents —
|
|
258
|
+
// an immediate read-back can serve the pre-edit blob. Poll until the edit lands.
|
|
259
|
+
const content = await waitFor(async () => {
|
|
260
|
+
const { data: fileData } = await octokit.rest.repos.getContent({
|
|
261
|
+
owner: state.owner,
|
|
262
|
+
repo: state.repo,
|
|
263
|
+
path: state.postPath,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const decoded = Buffer.from(fileData.content, 'base64').toString();
|
|
263
267
|
|
|
264
|
-
|
|
265
|
-
|
|
268
|
+
return decoded.includes('EDITED by the BEM test suite') ? decoded : false;
|
|
269
|
+
}, 60000, 3000);
|
|
266
270
|
|
|
267
271
|
assert.ok(
|
|
268
272
|
content.includes('EDITED by the BEM test suite'),
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Test email patterns - look like real emails but +bem suffix identifies them for cleanup.
|
|
10
|
-
// Fixed `
|
|
11
|
-
// state contamination in SendGrid/Beehiiv
|
|
12
|
-
|
|
10
|
+
// Fixed `itwcreativeworks.com` test domain — deterministic across brands (avoids cross-brand
|
|
11
|
+
// state contamination in SendGrid/Beehiiv) and we control it: mailbox verdicts are stable
|
|
12
|
+
// and the AI won't flag it as a placeholder (which `acme.com` now is — empty names,
|
|
13
|
+
// confidence 0 — breaking inference).
|
|
14
|
+
const TEST_DOMAIN = 'itwcreativeworks.com';
|
|
13
15
|
const TEST_EMAILS = {
|
|
14
16
|
valid: () => `sarah.martinez+bem@${TEST_DOMAIN}`, // Should infer: Sarah Martinez
|
|
15
17
|
invalid: () => `nonexistent.user+bem@${TEST_DOMAIN}`, // No such mailbox — mailbox verification should flag invalid
|
|
@@ -32,7 +34,11 @@ module.exports = {
|
|
|
32
34
|
const response = await http.command('general:add-marketing-contact', {
|
|
33
35
|
email: testEmail,
|
|
34
36
|
source: 'bem-test',
|
|
35
|
-
// No firstName/lastName - should be inferred
|
|
37
|
+
// No firstName/lastName - should be inferred
|
|
38
|
+
// skipValidation bypasses the mailbox verification check — the test email
|
|
39
|
+
// doesn't have a real mailbox so the provider (correctly) marks it as not
|
|
40
|
+
// deliverable. We're testing the command flow, not the deliverability check.
|
|
41
|
+
skipValidation: true,
|
|
36
42
|
});
|
|
37
43
|
|
|
38
44
|
assert.isSuccess(response, 'Add marketing contact should succeed for admin');
|
|
@@ -146,6 +152,10 @@ module.exports = {
|
|
|
146
152
|
email: testEmail,
|
|
147
153
|
source: 'bem-test',
|
|
148
154
|
// No firstName/lastName - should be inferred
|
|
155
|
+
// skipValidation bypasses the mailbox verification check — this test's concern
|
|
156
|
+
// is AI name inference (which runs after validation); without it the mailbox
|
|
157
|
+
// check 400s the fabricated address before the inference asserts run.
|
|
158
|
+
skipValidation: true,
|
|
149
159
|
});
|
|
150
160
|
|
|
151
161
|
assert.isSuccess(response, 'Add marketing contact should succeed');
|
|
@@ -227,6 +237,27 @@ module.exports = {
|
|
|
227
237
|
source: 'bem-test',
|
|
228
238
|
});
|
|
229
239
|
|
|
240
|
+
// Outcome A: the provider rejected the fabricated mailbox → admin 400. Every
|
|
241
|
+
// earlier check passes deterministically for this address (format/localPart/
|
|
242
|
+
// disposable/corporate return their own dedicated messages, typo can't match,
|
|
243
|
+
// and the domain has real MX), so reaching the generic validation-failed 400
|
|
244
|
+
// proves the paid mailbox pipeline ran and rejected. Nothing was added, so
|
|
245
|
+
// cleanup is a no-op.
|
|
246
|
+
if (response.status === 400) {
|
|
247
|
+
assert.ok(
|
|
248
|
+
response.error?.includes('Email validation failed'),
|
|
249
|
+
`400 should be the validation-failed branch, got: ${response.error}`
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (response.error.includes('(dns)')) {
|
|
253
|
+
skip('Transient DNS failure — mailbox check never ran');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
assert.ok(response.error.includes('(mailbox)'), `Failed check should be mailbox, got: ${response.error}`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Outcome B: provider accepted (valid/catch-all/unknown) or failed open.
|
|
230
261
|
assert.isSuccess(response, 'Add marketing contact should succeed');
|
|
231
262
|
|
|
232
263
|
// Check that validation info is included
|
|
@@ -238,9 +269,10 @@ module.exports = {
|
|
|
238
269
|
|
|
239
270
|
const mbResult = response.data.validation.checks.mailbox;
|
|
240
271
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
272
|
+
// Provider errors fail open as { valid: true, error, provider } with NO status
|
|
273
|
+
// field — out of credits, bad key, timeouts. Nothing assertable; skip.
|
|
274
|
+
if (mbResult.error) {
|
|
275
|
+
skip(`Mailbox verification unavailable: ${mbResult.error}`);
|
|
244
276
|
}
|
|
245
277
|
|
|
246
278
|
assert.hasProperty(mbResult, 'status', 'Mailbox check should return status');
|
|
@@ -268,7 +300,8 @@ module.exports = {
|
|
|
268
300
|
: false,
|
|
269
301
|
|
|
270
302
|
async run({ http, assert, skip }) {
|
|
271
|
-
//
|
|
303
|
+
// Email that should reach the mailbox provider and be flagged as undeliverable.
|
|
304
|
+
// Must NOT trip earlier checks (localPart blocklist, disposable, corporate).
|
|
272
305
|
const testEmail = TEST_EMAILS.invalid();
|
|
273
306
|
|
|
274
307
|
const response = await http.command('general:add-marketing-contact', {
|
|
@@ -276,20 +309,27 @@ module.exports = {
|
|
|
276
309
|
source: 'bem-test',
|
|
277
310
|
});
|
|
278
311
|
|
|
279
|
-
// Should still succeed (we fail open) but mailbox should report invalid
|
|
280
|
-
assert.isSuccess(response, 'Request should succeed even with invalid email');
|
|
281
|
-
|
|
282
312
|
const mbResult = response.data?.validation?.checks?.mailbox;
|
|
283
313
|
|
|
284
|
-
//
|
|
285
|
-
if (mbResult?.error
|
|
286
|
-
skip(
|
|
314
|
+
// Provider errors fail open with no status — the test can't exercise rejection; skip.
|
|
315
|
+
if (mbResult?.error) {
|
|
316
|
+
skip(`Mailbox verification unavailable: ${mbResult.error}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// If the response was a 400, that's the legitimate rejection path — done.
|
|
320
|
+
if (response.status === 400) {
|
|
321
|
+
if (response.error?.includes('(dns)')) {
|
|
322
|
+
skip('Transient DNS failure — mailbox check never ran');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
assert.ok(response.error?.includes('(mailbox)'), `Failed check should be mailbox, got: ${response.error}`);
|
|
326
|
+
return;
|
|
287
327
|
}
|
|
288
328
|
|
|
289
|
-
//
|
|
329
|
+
// Otherwise expect a 200 with a non-"valid" mailbox status.
|
|
330
|
+
assert.isSuccess(response, 'Request should succeed (fail-open) or error 400');
|
|
290
331
|
if (mbResult) {
|
|
291
332
|
assert.hasProperty(mbResult, 'status', 'Should have status');
|
|
292
|
-
// Status should NOT be 'valid' for this fake email
|
|
293
333
|
assert.notEqual(mbResult.status, 'valid', 'Fake email should not be marked valid');
|
|
294
334
|
}
|
|
295
335
|
},
|
|
@@ -245,10 +245,10 @@ module.exports = {
|
|
|
245
245
|
{
|
|
246
246
|
name: 'verify-edit',
|
|
247
247
|
auth: 'admin',
|
|
248
|
-
timeout:
|
|
248
|
+
timeout: 90000, // waitFor polls up to 60s for GitHub content propagation
|
|
249
249
|
skip: !process.env.GH_TOKEN ? 'GH_TOKEN env var not set' : false,
|
|
250
250
|
|
|
251
|
-
async run({ assert, state }) {
|
|
251
|
+
async run({ assert, state, waitFor }) {
|
|
252
252
|
if (!state.postPath || state.editSkipped) {
|
|
253
253
|
assert.ok(true, 'Skipping verify - edit was not performed');
|
|
254
254
|
return;
|
|
@@ -256,15 +256,19 @@ module.exports = {
|
|
|
256
256
|
|
|
257
257
|
const octokit = new Octokit({ auth: process.env.GH_TOKEN });
|
|
258
258
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
259
|
+
// GitHub's contents API is eventually consistent after createOrUpdateFileContents —
|
|
260
|
+
// an immediate read-back can serve the pre-edit blob. Poll until the edit lands.
|
|
261
|
+
const content = await waitFor(async () => {
|
|
262
|
+
const { data: fileData } = await octokit.rest.repos.getContent({
|
|
263
|
+
owner: state.owner,
|
|
264
|
+
repo: state.repo,
|
|
265
|
+
path: state.postPath,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const decoded = Buffer.from(fileData.content, 'base64').toString();
|
|
265
269
|
|
|
266
|
-
|
|
267
|
-
|
|
270
|
+
return decoded.includes('EDITED by the BEM test suite') ? decoded : false;
|
|
271
|
+
}, 60000, 3000);
|
|
268
272
|
|
|
269
273
|
assert.ok(
|
|
270
274
|
content.includes('EDITED by the BEM test suite'),
|