@terreno/api 0.13.3 → 0.14.1
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/dist/__tests__/versionCheckPlugin.test.js +136 -3
- package/dist/api.arrayOperations.test.js +1 -0
- package/dist/api.d.ts +15 -4
- package/dist/api.errors.test.js +1 -0
- package/dist/api.hooks.test.js +1 -0
- package/dist/api.js +153 -104
- package/dist/api.query.test.js +1 -0
- package/dist/api.test.js +174 -0
- package/dist/auth.d.ts +10 -5
- package/dist/auth.js +163 -90
- package/dist/auth.test.js +159 -0
- package/dist/betterAuthApp.test.js +1 -0
- package/dist/betterAuthSetup.d.ts +5 -6
- package/dist/betterAuthSetup.js +30 -17
- package/dist/betterAuthSetup.test.js +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.js +257 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +328 -0
- package/dist/configuration.test.js +1 -0
- package/dist/configurationApp.d.ts +1 -1
- package/dist/configurationApp.js +17 -13
- package/dist/configurationPlugin.test.js +1 -0
- package/dist/consentApp.test.js +1 -0
- package/dist/envConfigurationPlugin.d.ts +2 -0
- package/dist/envConfigurationPlugin.js +173 -0
- package/dist/envConfigurationPlugin.test.d.ts +1 -0
- package/dist/envConfigurationPlugin.test.js +322 -0
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +111 -12
- package/dist/errors.test.js +16 -1
- package/dist/example.js +19 -7
- package/dist/expressServer.d.ts +10 -9
- package/dist/expressServer.js +62 -53
- package/dist/expressServer.test.js +165 -2
- package/dist/githubAuth.d.ts +2 -1
- package/dist/githubAuth.js +41 -26
- package/dist/githubAuth.test.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +42 -20
- package/dist/models/versionConfig.d.ts +2 -0
- package/dist/models/versionConfig.js +8 -0
- package/dist/notifiers/googleChatNotifier.js +14 -16
- package/dist/notifiers/googleChatNotifier.test.js +1 -0
- package/dist/notifiers/slackNotifier.js +16 -14
- package/dist/notifiers/slackNotifier.test.js +41 -3
- package/dist/notifiers/zoomNotifier.js +7 -10
- package/dist/notifiers/zoomNotifier.test.js +1 -0
- package/dist/openApi.d.ts +1 -1
- package/dist/openApi.test.js +1 -0
- package/dist/openApiBuilder.d.ts +39 -6
- package/dist/openApiBuilder.js +1 -31
- package/dist/openApiBuilder.test.js +1 -0
- package/dist/openApiValidator.js +1 -0
- package/dist/openApiValidator.test.js +1 -0
- package/dist/permissions.d.ts +4 -4
- package/dist/permissions.js +67 -65
- package/dist/permissions.middleware.test.js +1 -0
- package/dist/permissions.test.js +1 -0
- package/dist/plugins.d.ts +5 -5
- package/dist/plugins.js +18 -9
- package/dist/plugins.test.js +1 -1
- package/dist/populate.d.ts +15 -8
- package/dist/populate.js +23 -24
- package/dist/populate.test.js +1 -0
- package/dist/realtime/changeStreamWatcher.d.ts +73 -0
- package/dist/realtime/changeStreamWatcher.js +724 -0
- package/dist/realtime/index.d.ts +6 -0
- package/dist/realtime/index.js +27 -0
- package/dist/realtime/queryMatcher.d.ts +14 -0
- package/dist/realtime/queryMatcher.js +250 -0
- package/dist/realtime/queryStore.d.ts +37 -0
- package/dist/realtime/queryStore.js +195 -0
- package/dist/realtime/realtime.test.d.ts +10 -0
- package/dist/realtime/realtime.test.js +3066 -0
- package/dist/realtime/realtimeApp.d.ts +93 -0
- package/dist/realtime/realtimeApp.js +560 -0
- package/dist/realtime/registry.d.ts +40 -0
- package/dist/realtime/registry.js +38 -0
- package/dist/realtime/socketUser.d.ts +10 -0
- package/dist/realtime/socketUser.js +17 -0
- package/dist/realtime/types.d.ts +100 -0
- package/dist/realtime/types.js +2 -0
- package/dist/requestContext.d.ts +37 -0
- package/dist/requestContext.js +344 -0
- package/dist/requestContext.test.d.ts +1 -0
- package/dist/requestContext.test.js +384 -0
- package/dist/terrenoApp.d.ts +8 -0
- package/dist/terrenoApp.js +50 -13
- package/dist/terrenoApp.test.js +194 -21
- package/dist/terrenoPlugin.d.ts +11 -0
- package/dist/tests/bunSetup.js +1 -0
- package/dist/tests.js +1 -1
- package/dist/transformers.d.ts +2 -2
- package/dist/transformers.js +5 -3
- package/dist/transformers.test.js +90 -0
- package/dist/types/consentResponse.d.ts +6 -3
- package/dist/versionCheckPlugin.d.ts +2 -0
- package/dist/versionCheckPlugin.js +18 -12
- package/package.json +4 -2
- package/src/__tests__/versionCheckPlugin.test.ts +94 -3
- package/src/api.arrayOperations.test.ts +1 -0
- package/src/api.errors.test.ts +1 -0
- package/src/api.hooks.test.ts +1 -0
- package/src/api.query.test.ts +1 -0
- package/src/api.test.ts +132 -0
- package/src/api.ts +199 -84
- package/src/auth.test.ts +160 -0
- package/src/auth.ts +120 -50
- package/src/betterAuthApp.test.ts +1 -0
- package/src/betterAuthSetup.test.ts +1 -0
- package/src/betterAuthSetup.ts +59 -22
- package/src/config.test.ts +255 -0
- package/src/config.ts +216 -0
- package/src/configuration.test.ts +1 -0
- package/src/configurationApp.ts +59 -24
- package/src/configurationPlugin.test.ts +1 -0
- package/src/consentApp.test.ts +1 -0
- package/src/envConfigurationPlugin.test.ts +143 -0
- package/src/envConfigurationPlugin.ts +100 -0
- package/src/errors.test.ts +19 -1
- package/src/errors.ts +118 -38
- package/src/example.ts +49 -21
- package/src/express.d.ts +18 -1
- package/src/expressServer.test.ts +147 -2
- package/src/expressServer.ts +80 -50
- package/src/githubAuth.test.ts +1 -0
- package/src/githubAuth.ts +59 -38
- package/src/index.ts +4 -0
- package/src/logger.ts +47 -17
- package/src/models/versionConfig.ts +13 -2
- package/src/notifiers/googleChatNotifier.test.ts +1 -0
- package/src/notifiers/googleChatNotifier.ts +7 -9
- package/src/notifiers/slackNotifier.test.ts +29 -3
- package/src/notifiers/slackNotifier.ts +9 -7
- package/src/notifiers/zoomNotifier.test.ts +1 -0
- package/src/notifiers/zoomNotifier.ts +8 -11
- package/src/openApi.test.ts +1 -0
- package/src/openApi.ts +4 -4
- package/src/openApiBuilder.test.ts +1 -0
- package/src/openApiBuilder.ts +14 -11
- package/src/openApiValidator.test.ts +1 -0
- package/src/openApiValidator.ts +3 -2
- package/src/permissions.middleware.test.ts +1 -0
- package/src/permissions.test.ts +1 -0
- package/src/permissions.ts +30 -25
- package/src/plugins.test.ts +1 -1
- package/src/plugins.ts +21 -14
- package/src/populate.test.ts +1 -0
- package/src/populate.ts +44 -36
- package/src/realtime/changeStreamWatcher.ts +572 -0
- package/src/realtime/index.ts +34 -0
- package/src/realtime/queryMatcher.ts +179 -0
- package/src/realtime/queryStore.ts +132 -0
- package/src/realtime/realtime.test.ts +2465 -0
- package/src/realtime/realtimeApp.ts +478 -0
- package/src/realtime/registry.ts +64 -0
- package/src/realtime/socketUser.ts +25 -0
- package/src/realtime/types.ts +112 -0
- package/src/requestContext.test.ts +321 -0
- package/src/requestContext.ts +368 -0
- package/src/terrenoApp.test.ts +137 -11
- package/src/terrenoApp.ts +64 -17
- package/src/terrenoPlugin.ts +12 -0
- package/src/tests/bunSetup.ts +1 -0
- package/src/tests.ts +7 -2
- package/src/transformers.test.ts +70 -2
- package/src/transformers.ts +15 -7
- package/src/types/consentResponse.ts +8 -10
- package/src/versionCheckPlugin.ts +15 -7
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
1
2
|
import {afterEach, beforeEach, describe, expect, it} from "bun:test";
|
|
2
3
|
import supertest from "supertest";
|
|
3
4
|
import {VersionConfig} from "../models/versionConfig";
|
|
@@ -29,7 +30,7 @@ describe("VersionCheckPlugin", () => {
|
|
|
29
30
|
it("returns ok when no VersionConfig exists", async () => {
|
|
30
31
|
const res = await app.get("/version-check").query({platform: "web", version: 100});
|
|
31
32
|
expect(res.status).toBe(200);
|
|
32
|
-
expect(res.body).toEqual({status: "ok"});
|
|
33
|
+
expect(res.body).toEqual({pollingIntervalMs: 86400000, status: "ok"});
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
it("returns ok when version param is missing", async () => {
|
|
@@ -52,7 +53,12 @@ describe("VersionCheckPlugin", () => {
|
|
|
52
53
|
|
|
53
54
|
const res = await app.get("/version-check").query({platform: "web", version: 150});
|
|
54
55
|
expect(res.status).toBe(200);
|
|
55
|
-
expect(res.body).toEqual({
|
|
56
|
+
expect(res.body).toEqual({
|
|
57
|
+
pollingIntervalMs: 86400000,
|
|
58
|
+
requiredVersion: 50,
|
|
59
|
+
status: "ok",
|
|
60
|
+
warningVersion: 100,
|
|
61
|
+
});
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
it("returns warning when client version < warning (web)", async () => {
|
|
@@ -116,7 +122,74 @@ describe("VersionCheckPlugin", () => {
|
|
|
116
122
|
|
|
117
123
|
const res = await app.get("/version-check").query({platform: "web", version: 100});
|
|
118
124
|
expect(res.status).toBe(200);
|
|
119
|
-
expect(res.body).toEqual({
|
|
125
|
+
expect(res.body).toEqual({
|
|
126
|
+
pollingIntervalMs: 86400000,
|
|
127
|
+
requiredVersion: 50,
|
|
128
|
+
status: "ok",
|
|
129
|
+
warningVersion: 100,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns pollingIntervalMs from config pollingIntervalMinutes", async () => {
|
|
134
|
+
await VersionConfig.create({
|
|
135
|
+
pollingIntervalMinutes: 60,
|
|
136
|
+
webRequiredVersion: 0,
|
|
137
|
+
webWarningVersion: 0,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const res = await app.get("/version-check").query({platform: "web", version: 100});
|
|
141
|
+
expect(res.status).toBe(200);
|
|
142
|
+
expect(res.body.pollingIntervalMs).toBe(3600000);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("returns default pollingIntervalMs (86400000) when pollingIntervalMinutes not set", async () => {
|
|
146
|
+
await VersionConfig.create({
|
|
147
|
+
webRequiredVersion: 0,
|
|
148
|
+
webWarningVersion: 0,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const res = await app.get("/version-check").query({platform: "web", version: 100});
|
|
152
|
+
expect(res.status).toBe(200);
|
|
153
|
+
expect(res.body.pollingIntervalMs).toBe(86400000);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("handles numeric version parameter directly", async () => {
|
|
157
|
+
await VersionConfig.create({
|
|
158
|
+
webRequiredVersion: 100,
|
|
159
|
+
webWarningVersion: 150,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const res = await app.get("/version-check?version=50&platform=web");
|
|
163
|
+
expect(res.status).toBe(200);
|
|
164
|
+
expect(res.body.status).toBe("required");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("returns default warning message when warningMessage not set", async () => {
|
|
168
|
+
await VersionConfig.create({
|
|
169
|
+
webRequiredVersion: 0,
|
|
170
|
+
webWarningVersion: 100,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const res = await app.get("/version-check").query({platform: "web", version: 50});
|
|
174
|
+
expect(res.status).toBe(200);
|
|
175
|
+
expect(res.body.status).toBe("warning");
|
|
176
|
+
expect(res.body.message).toBe(
|
|
177
|
+
"A new version is available. Please update for the best experience."
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("returns default required message when requiredMessage not set", async () => {
|
|
182
|
+
await VersionConfig.create({
|
|
183
|
+
webRequiredVersion: 100,
|
|
184
|
+
webWarningVersion: 150,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const res = await app.get("/version-check").query({platform: "web", version: 50});
|
|
188
|
+
expect(res.status).toBe(200);
|
|
189
|
+
expect(res.body.status).toBe("required");
|
|
190
|
+
expect(res.body.message).toBe(
|
|
191
|
+
"This version is no longer supported. Please update to continue."
|
|
192
|
+
);
|
|
120
193
|
});
|
|
121
194
|
|
|
122
195
|
it("version equal to required returns warning not required", async () => {
|
|
@@ -130,3 +203,21 @@ describe("VersionCheckPlugin", () => {
|
|
|
130
203
|
expect(res.body.status).toBe("warning");
|
|
131
204
|
});
|
|
132
205
|
});
|
|
206
|
+
|
|
207
|
+
describe("VersionCheckPlugin direct usage", () => {
|
|
208
|
+
it("can be instantiated and register called directly on an express app", async () => {
|
|
209
|
+
const express = require("express");
|
|
210
|
+
const plugin = new VersionCheckPlugin();
|
|
211
|
+
expect(plugin).toBeDefined();
|
|
212
|
+
expect(plugin).toBeInstanceOf(VersionCheckPlugin);
|
|
213
|
+
expect(typeof plugin.register).toBe("function");
|
|
214
|
+
|
|
215
|
+
const expressApp = express();
|
|
216
|
+
plugin.register(expressApp);
|
|
217
|
+
|
|
218
|
+
const testApp = supertest(expressApp);
|
|
219
|
+
const res = await testApp.get("/version-check");
|
|
220
|
+
expect(res.status).toBe(200);
|
|
221
|
+
expect(res.body.status).toBe("ok");
|
|
222
|
+
});
|
|
223
|
+
});
|
package/src/api.errors.test.ts
CHANGED
package/src/api.hooks.test.ts
CHANGED
package/src/api.query.test.ts
CHANGED
package/src/api.test.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
// biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
|
|
2
|
+
// biome-ignore-all lint/suspicious/noImplicitAnyLet: test mock typing
|
|
1
3
|
import {beforeEach, describe, expect, it} from "bun:test";
|
|
2
4
|
import type express from "express";
|
|
5
|
+
import {DateTime} from "luxon";
|
|
6
|
+
import type mongoose from "mongoose";
|
|
3
7
|
import supertest from "supertest";
|
|
4
8
|
import type TestAgent from "supertest/lib/agent";
|
|
5
9
|
|
|
@@ -14,6 +18,7 @@ import {
|
|
|
14
18
|
getBaseServer,
|
|
15
19
|
RequiredModel,
|
|
16
20
|
setupDb,
|
|
21
|
+
type User,
|
|
17
22
|
UserModel,
|
|
18
23
|
} from "./tests";
|
|
19
24
|
import {AdminOwnerTransformer} from "./transformers";
|
|
@@ -1901,4 +1906,131 @@ describe("@terreno/api", () => {
|
|
|
1901
1906
|
expect(res.body.title).toContain("preUpdate hook error");
|
|
1902
1907
|
});
|
|
1903
1908
|
});
|
|
1909
|
+
|
|
1910
|
+
describe("conflict detection (If-Unmodified-Since)", () => {
|
|
1911
|
+
let admin: mongoose.HydratedDocument<User>;
|
|
1912
|
+
let _notAdmin: mongoose.HydratedDocument<User>;
|
|
1913
|
+
let agent: TestAgent;
|
|
1914
|
+
let spinach: Food;
|
|
1915
|
+
|
|
1916
|
+
beforeEach(async () => {
|
|
1917
|
+
[admin, _notAdmin] = await setupDb();
|
|
1918
|
+
|
|
1919
|
+
spinach = await FoodModel.create({
|
|
1920
|
+
calories: 10,
|
|
1921
|
+
created: DateTime.fromISO("2025-06-15T12:00:00.000Z").toJSDate(),
|
|
1922
|
+
hidden: false,
|
|
1923
|
+
name: "Spinach",
|
|
1924
|
+
ownerId: admin._id,
|
|
1925
|
+
});
|
|
1926
|
+
await FoodModel.collection.updateOne(
|
|
1927
|
+
{_id: spinach._id as unknown as mongoose.Types.ObjectId},
|
|
1928
|
+
{$set: {updated: DateTime.fromISO("2025-06-15T12:00:00.000Z").toJSDate()}}
|
|
1929
|
+
);
|
|
1930
|
+
|
|
1931
|
+
app = getBaseServer();
|
|
1932
|
+
setupAuth(app, UserModel as unknown as Parameters<typeof setupAuth>[1]);
|
|
1933
|
+
addAuthRoutes(app, UserModel as unknown as Parameters<typeof addAuthRoutes>[1]);
|
|
1934
|
+
app.use(
|
|
1935
|
+
"/food",
|
|
1936
|
+
modelRouter(FoodModel, {
|
|
1937
|
+
permissions: {
|
|
1938
|
+
create: [Permissions.IsAny],
|
|
1939
|
+
delete: [Permissions.IsAny],
|
|
1940
|
+
list: [Permissions.IsAny],
|
|
1941
|
+
read: [Permissions.IsAny],
|
|
1942
|
+
update: [Permissions.IsAny],
|
|
1943
|
+
},
|
|
1944
|
+
})
|
|
1945
|
+
);
|
|
1946
|
+
server = supertest(app);
|
|
1947
|
+
agent = await authAsUser(app, "admin");
|
|
1948
|
+
});
|
|
1949
|
+
|
|
1950
|
+
it("returns 409 when If-Unmodified-Since is older than doc.updated", async () => {
|
|
1951
|
+
const staleTimestamp = DateTime.fromISO("2025-06-15T11:00:00.000Z").toHTTP();
|
|
1952
|
+
|
|
1953
|
+
const res = await agent
|
|
1954
|
+
.patch(`/food/${spinach._id}`)
|
|
1955
|
+
.set("If-Unmodified-Since", staleTimestamp)
|
|
1956
|
+
.send({name: "Should Fail"})
|
|
1957
|
+
.expect(409);
|
|
1958
|
+
|
|
1959
|
+
expect(res.body.error).toBe("Conflict");
|
|
1960
|
+
expect(res.body.message).toBe("Document was modified since your last read");
|
|
1961
|
+
expect(res.body.data).toBeDefined();
|
|
1962
|
+
// The response should contain the current server version
|
|
1963
|
+
expect(res.body.data.name).toBe("Spinach");
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
it("succeeds when If-Unmodified-Since matches or is newer than doc.updated", async () => {
|
|
1967
|
+
const freshTimestamp = DateTime.fromISO("2025-06-15T13:00:00.000Z").toHTTP();
|
|
1968
|
+
|
|
1969
|
+
const res = await agent
|
|
1970
|
+
.patch(`/food/${spinach._id}`)
|
|
1971
|
+
.set("If-Unmodified-Since", freshTimestamp)
|
|
1972
|
+
.send({name: "Updated Spinach"})
|
|
1973
|
+
.expect(200);
|
|
1974
|
+
|
|
1975
|
+
expect(res.body.data.name).toBe("Updated Spinach");
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
it("succeeds normally when If-Unmodified-Since header is not present", async () => {
|
|
1979
|
+
const res = await agent
|
|
1980
|
+
.patch(`/food/${spinach._id}`)
|
|
1981
|
+
.send({name: "No Header Update"})
|
|
1982
|
+
.expect(200);
|
|
1983
|
+
|
|
1984
|
+
expect(res.body.data.name).toBe("No Header Update");
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
it("succeeds when If-Unmodified-Since exactly matches doc.updated", async () => {
|
|
1988
|
+
const exactTimestamp = DateTime.fromISO("2025-06-15T12:00:00.000Z").toHTTP();
|
|
1989
|
+
|
|
1990
|
+
const res = await agent
|
|
1991
|
+
.patch(`/food/${spinach._id}`)
|
|
1992
|
+
.set("If-Unmodified-Since", exactTimestamp)
|
|
1993
|
+
.send({name: "Exact Match"})
|
|
1994
|
+
.expect(200);
|
|
1995
|
+
|
|
1996
|
+
expect(res.body.data.name).toBe("Exact Match");
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
it("prefers precise conflict timestamp header when present", async () => {
|
|
2000
|
+
const roundedStaleTimestamp = DateTime.fromISO("2025-06-15T11:59:59.000Z").toHTTP();
|
|
2001
|
+
|
|
2002
|
+
const res = await agent
|
|
2003
|
+
.patch(`/food/${spinach._id}`)
|
|
2004
|
+
.set("If-Unmodified-Since", roundedStaleTimestamp)
|
|
2005
|
+
.set("X-Unmodified-Since-ISO", "2025-06-15T12:00:00.750Z")
|
|
2006
|
+
.send({name: "Precise Match"})
|
|
2007
|
+
.expect(200);
|
|
2008
|
+
|
|
2009
|
+
expect(res.body.data.name).toBe("Precise Match");
|
|
2010
|
+
});
|
|
2011
|
+
|
|
2012
|
+
it("returns 409 when precise conflict timestamp is older than doc.updated", async () => {
|
|
2013
|
+
await agent
|
|
2014
|
+
.patch(`/food/${spinach._id}`)
|
|
2015
|
+
.set("If-Unmodified-Since", DateTime.fromISO("2025-06-15T12:00:01.000Z").toHTTP()!)
|
|
2016
|
+
.set("X-Unmodified-Since-ISO", "2025-06-15T11:59:59.500Z")
|
|
2017
|
+
.send({name: "Precise Stale"})
|
|
2018
|
+
.expect(409);
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
it("falls back to doc.created when doc.updated is unavailable", async () => {
|
|
2022
|
+
await FoodModel.collection.updateOne(
|
|
2023
|
+
{_id: spinach._id as unknown as mongoose.Types.ObjectId},
|
|
2024
|
+
{$unset: {updated: ""}}
|
|
2025
|
+
);
|
|
2026
|
+
|
|
2027
|
+
const res = await agent
|
|
2028
|
+
.patch(`/food/${spinach._id}`)
|
|
2029
|
+
.set("If-Unmodified-Since", DateTime.fromISO("2025-06-15T11:59:59.999Z").toHTTP()!)
|
|
2030
|
+
.send({name: "Created Fallback"})
|
|
2031
|
+
.expect(409);
|
|
2032
|
+
|
|
2033
|
+
expect(res.body.data.name).toBe("Spinach");
|
|
2034
|
+
});
|
|
2035
|
+
});
|
|
1904
2036
|
});
|