Skip to content
Merged
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ apps/mobile/android/
apps/mobile/.expo/
apps/mobile/.gradle/
infra/docker/data
infra/docker/overrides
infra/docker/.trusted-config-generations/
infra/docker/.trusted-config-current
infra/docker/.release-evidence/
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ dist/
# Downloaded source data (managed by data-manager service / `openmapx data` CLI)
infra/docker/data/
infra/docker/state/
# The operator's whole-file replacements of release service files
infra/docker/overrides/

# Community integrations (user-installed, not version-controlled)
custom_integrations/
Expand Down
300 changes: 300 additions & 0 deletions apps/api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -27692,6 +27692,66 @@
}
}
},
"409": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ok": {
"type": "boolean",
"enum": [
false
]
},
"message": {
"type": "string"
},
"jobId": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"statusCode",
"error",
"message"
],
"additionalProperties": false
}
]
}
}
}
},
"413": {
"description": "Response",
"content": {
Expand Down Expand Up @@ -28217,6 +28277,66 @@
}
}
},
"409": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ok": {
"type": "boolean",
"enum": [
false
]
},
"message": {
"type": "string"
},
"jobId": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"statusCode",
"error",
"message"
],
"additionalProperties": false
}
]
}
}
}
},
"413": {
"description": "Response",
"content": {
Expand Down Expand Up @@ -28742,6 +28862,66 @@
}
}
},
"409": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ok": {
"type": "boolean",
"enum": [
false
]
},
"message": {
"type": "string"
},
"jobId": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"statusCode",
"error",
"message"
],
"additionalProperties": false
}
]
}
}
}
},
"413": {
"description": "Response",
"content": {
Expand Down Expand Up @@ -43259,6 +43439,66 @@
}
}
},
"409": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ok": {
"type": "boolean",
"enum": [
false
]
},
"message": {
"type": "string"
},
"jobId": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"statusCode",
"error",
"message"
],
"additionalProperties": false
}
]
}
}
}
},
"413": {
"description": "Response",
"content": {
Expand Down Expand Up @@ -46457,6 +46697,66 @@
}
}
},
"409": {
"description": "Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"error": {
"type": "string"
},
"ok": {
"type": "boolean",
"enum": [
false
]
},
"message": {
"type": "string"
},
"jobId": {
"type": "string"
}
},
"required": [
"error"
],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"minimum": 400,
"maximum": 599
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"statusCode",
"error",
"message"
],
"additionalProperties": false
}
]
}
}
}
},
"413": {
"description": "Response",
"content": {
Expand Down
22 changes: 22 additions & 0 deletions apps/api/src/routes/__tests__/admin-services.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,28 @@ describe("service credentials", () => {
expect(mockRestoreServiceConfig).toHaveBeenCalledTimes(outcome === "not_committed" ? 1 : 0);
},
);
it("answers an apply the operator can fix with its reason, after restoring what was saved", async () => {
const problem =
"infra/docker/overrides/services/martin/config/martin.yaml can't be used: it is a symbolic link. Render refuses it rather than use the release's file in its place: make it a regular file of yours that only you may write, or delete it.";
vi.mocked(applyTrustedConfiguration).mockRejectedValueOnce(
new TrustedConfigurationApplyError({
outcome: "not_committed",
reason: "failed",
cleanupFailed: false,
publisherCleanupFailed: false,
registryUpdateFailed: false,
problem,
}),
);
const res = await app.inject({
method: "POST",
url: "/admin/services/openconditions-ingest/config",
payload: { config: {} },
});
expect(res.statusCode).toBe(409);
expect(res.json()).toEqual({ error: problem });
expect(mockRestoreServiceConfig).toHaveBeenCalledTimes(1);
});
it("persists only the route-validated update through the atomic writer", async () => {
mockValidateConfigBody.mockReturnValueOnce({
updates: { RATE_LIMIT_MAX: 240 },
Expand Down
Loading
Loading