Page MenuHomeSealhub

D200.id700.diff
No OneTemporary

D200.id700.diff

diff --git a/lib/app/app.js b/lib/app/app.js
--- a/lib/app/app.js
+++ b/lib/app/app.js
@@ -17,6 +17,7 @@
const WwwServerFactory = locreq("lib/http/http.js");
const DatastoreMongoFactory = locreq("lib/datastore/db.js");
+const MetadataFactory = locreq("lib/app/metadata.js");
const load_base_chips = locreq("lib/app/load-base-chips");
const default_config = locreq("default_config.json");
@@ -26,6 +27,7 @@
class App {
constructor(custom_config, manifest) {
+ this.status = "stopped";
this.Sealious = Sealious;
this.setupEventEmitter();
@@ -55,6 +57,7 @@
load_base_chips(this);
this.Datastore = new DatastoreMongoFactory(this);
+ this.Metadata = MetadataFactory(this);
this.FieldType = Sealious.FieldType.bind(Sealious.FieldType, this);
this.Collection = Sealious.Collection.bind(Sealious.Collection, this);
@@ -107,6 +110,7 @@
}
async start() {
+ this.status = "starting";
assert(
["dev", "production"].includes(
this.ConfigManager.get("core.environment")
@@ -118,13 +122,16 @@
await this.Mail.init();
await this.ChipManager.start_chips();
await this.emit("start");
+ this.status = "running";
return this;
}
async stop() {
+ this.status = "stopping";
await this.emit("stop");
await this.WwwServer.stop();
await this.Datastore.stop();
+ this.status = "stopped";
}
}
diff --git a/lib/app/base-chips/collections/password-reset-intents.subtest.js b/lib/app/base-chips/collections/password-reset-intents.subtest.js
--- a/lib/app/base-chips/collections/password-reset-intents.subtest.js
+++ b/lib/app/base-chips/collections/password-reset-intents.subtest.js
@@ -2,7 +2,7 @@
const axios = require("axios");
const assert = require("assert");
const { promise_timeout } = locreq("test_utils");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("password-reset-intents", () => {
async function create_a_user(app) {
@@ -19,7 +19,7 @@
}
it("tells you if the email address doesn't exist", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
try {
await axios.post(
`${base_url}/api/v1/collections/password-reset-intents`,
@@ -38,7 +38,7 @@
}));
it("allows anyone to create an intent, if the email exists", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
await create_a_user(app);
const data = (await axios.post(
`${base_url}/api/v1/collections/password-reset-intents`,
@@ -53,7 +53,7 @@
}));
it("tells you if the email address is malformed", async () =>
- with_test_app(async ({ base_url }) => {
+ with_running_app(async ({ base_url }) => {
try {
await axios.post(
`${base_url}/api/v1/collections/password-reset-intents`,
@@ -72,7 +72,7 @@
}));
it("sends an email with the reset password link", async () =>
- with_test_app(async ({ app, base_url, mail_api }) => {
+ with_running_app(async ({ app, base_url, mail_api }) => {
await create_a_user(app);
const data = (await axios.post(
`${base_url}/api/v1/collections/password-reset-intents`,
diff --git a/lib/app/base-chips/collections/users.subtest.js b/lib/app/base-chips/collections/users.subtest.js
--- a/lib/app/base-chips/collections/users.subtest.js
+++ b/lib/app/base-chips/collections/users.subtest.js
@@ -1,11 +1,11 @@
const locreq = require("locreq")(__dirname);
const assert = require("assert");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("users", () => {
describe("auto create admin", () => {
it("should automatically create a registration intent for the admin user", async () =>
- with_test_app(async ({ app, mail_api }) => {
+ with_running_app(async ({ app, mail_api }) => {
const registration_intents = await app.run_action(
new app.Sealious.SuperContext(),
["collections", "registration-intents"],
diff --git a/lib/app/base-chips/field-types/field-types.test.js b/lib/app/base-chips/field-types/field-types.test.js
--- a/lib/app/base-chips/field-types/field-types.test.js
+++ b/lib/app/base-chips/field-types/field-types.test.js
@@ -1,4 +1,5 @@
describe("field types", () => {
require("./single_reference.subtest.js");
require("./text.subtest.js");
+ require("./reverse-single-reference.subtest.js");
});
diff --git a/lib/app/base-chips/field-types/reverse-single-reference.js b/lib/app/base-chips/field-types/reverse-single-reference.js
new file mode 100644
--- /dev/null
+++ b/lib/app/base-chips/field-types/reverse-single-reference.js
@@ -0,0 +1,211 @@
+"use strict";
+const locreq = require("locreq")(__dirname);
+const Promise = require("bluebird");
+const Errors = locreq("lib/response/error.js");
+const assert = require("assert");
+
+function params_to_cache_key(collection, field_name, params) {
+ return `${collection.name}___${field_name}-reverse-single-reference(${
+ params.collection.name
+ },${params.collection.field_name}).last_update`;
+}
+
+async function update_cache(
+ app,
+ collection,
+ field_name,
+ params,
+ resource_ids = undefined
+) {
+ let pipeline;
+ const referencing_field_name = params.field_name;
+ const referencing_collection = params.collection;
+ if (resource_ids) {
+ assert(Array.isArray(resource_ids));
+ pipeline = [
+ { $match: { [`body.${referencing_field_name}`]: { $in: resource_ids } } },
+ ];
+ } else {
+ pipeline = [];
+ }
+ pipeline.push({
+ $group: {
+ _id: `$body.${referencing_field_name}`,
+ referenced_by: { $push: `$sealious_id` },
+ },
+ });
+ const to_update = await app.Datastore.aggregate(
+ referencing_collection.name,
+ pipeline
+ );
+ if (resource_ids) {
+ for (let resource_id of resource_ids) {
+ if (to_update.filter(e => e._id === resource_id).length === 0) {
+ to_update.push({ _id: resource_id, referenced_by: [] });
+ }
+ }
+ }
+ for (let entry of to_update) {
+ await app.Datastore.update(
+ collection.name,
+ { sealious_id: entry._id },
+ { $set: { [`body.${field_name}`]: entry.referenced_by } }
+ );
+ }
+}
+
+const reverse_single_reference_factory = app => {
+ return {
+ name: "reverse-single-reference",
+
+ get_description: function() {
+ return "Shows which resources from given collection point to this resource in a given field.";
+ },
+
+ get_default_value: async () => [],
+
+ is_proper_value: function(context, params, new_value) {
+ return context.is_super
+ ? Promise.resolve()
+ : Promise.reject("This is a read-only field");
+ },
+
+ filter_to_query: async function(context, params, field_filter) {
+ if (typeof field_filter !== "object") {
+ return {
+ $eq: field_filter,
+ };
+ }
+ const matches = await app.run_action(
+ context,
+ ["collections", params.collection.name],
+ "show",
+ {
+ filter: field_filter,
+ }
+ );
+ const ids = matches.map(resource => resource.id);
+ return {
+ $in: ids,
+ };
+ },
+
+ format: function(context, params, decoded_value, format) {
+ // format can be "expand" or "deep-expand:<depth>", like "deep-expand:3"
+ if (!format) {
+ return decoded_value; // just the IDs
+ }
+
+ const format_params = format.split(":");
+
+ if (!["expand", "deep-expand"].includes(format_params[0])) {
+ return decoded_value;
+ }
+
+ const query_format = {};
+ if (format_params[0] === "deep-expand" && format_params[1] > 1) {
+ for (const field_name in params.collection.fields) {
+ const field = params.collection.fields[field_name];
+ if (field.type.name === "single_reference") {
+ query_format[field_name] = `deep-expand:${parseInt(
+ format_params[1],
+ 10
+ ) - 1}`;
+ }
+ }
+ }
+ const resource_ids = decoded_value;
+ return Promise.map(resource_ids, async resource_id =>
+ app.run_action(
+ context,
+ ["collections", params.collection.name, resource_id],
+ "show",
+ { format: query_format }
+ )
+ );
+ },
+
+ init: async (collection, field_name, params) => {
+ assert(
+ params.collection instanceof app.Sealious.Collection,
+ "'params.collection' should be an instance of Collection"
+ );
+ assert(
+ params.collection.fields[params.field_name],
+ `Collection '${
+ params.collection.name
+ }' does not contain a field named ${params.field_name}.`
+ );
+ app.on("start", async () => {
+ const last_modified_resource_in_reference_collection = (await app.run_action(
+ new app.Sealious.SuperContext(),
+ ["collections", params.collection.name],
+ "show",
+ {
+ sort: { "last_modified_context.timestamp": "desc" },
+ pagination: { items: 1 },
+ }
+ ))[0];
+
+ if (last_modified_resource_in_reference_collection) {
+ const last_modified_resource_timestamp =
+ last_modified_resource_in_reference_collection.last_modified_context
+ .timestamp;
+ const last_field_cache_update =
+ (await app.Metadata.get(
+ params_to_cache_key(collection, field_name, params)
+ )) || 0;
+ if (last_modified_resource_timestamp > last_field_cache_update) {
+ await update_cache(app, collection, field_name, params);
+ await app.Metadata.set(
+ params_to_cache_key(collection, field_name, params),
+ Date.now()
+ );
+ }
+ }
+ });
+ app.on(
+ new RegExp(`post:collections\.${params.collection.name}:create`),
+ async (path, event_params, resource) => {
+ const referenced_id = resource.body[params.field_name];
+ await update_cache(app, collection, field_name, params, [
+ referenced_id,
+ ]);
+ }
+ );
+ app.on(
+ new RegExp(`post:collections\.${params.collection.name}\..*:delete`),
+ async (path, event_params, resource) => {
+ const deleted_id = path[2];
+ const affected = await app.Datastore.find(collection.name, {
+ [`body.${field_name}`]: deleted_id,
+ });
+ const affected_ids = affected.map(document => document.sealious_id);
+ await update_cache(app, collection, field_name, params, affected_ids);
+ }
+ );
+ app.on(
+ new RegExp(`post:collections\.${params.collection.name}\..*:edit`),
+ async (path, event_params, resource) => {
+ if (!event_params.hasOwnProperty(params.field_name)) return;
+ const edited_id = path[2];
+ const no_longer_referenced = await app.Datastore.find(
+ collection.name,
+ {
+ [`body.${field_name}`]: edited_id,
+ }
+ );
+ const affected_ids = no_longer_referenced.map(
+ document => document.sealious_id
+ );
+ if (event_params[params.field_name]) {
+ affected_ids.push(event_params[params.field_name]);
+ }
+ await update_cache(app, collection, field_name, params, affected_ids);
+ }
+ );
+ },
+ };
+};
+
+module.exports = reverse_single_reference_factory;
diff --git a/lib/app/base-chips/field-types/reverse-single-reference.subtest.js b/lib/app/base-chips/field-types/reverse-single-reference.subtest.js
new file mode 100644
--- /dev/null
+++ b/lib/app/base-chips/field-types/reverse-single-reference.subtest.js
@@ -0,0 +1,185 @@
+const assert = require("assert");
+const Promise = require("bluebird");
+const locreq = require("locreq")(__dirname);
+const axios = require("axios");
+const { create_resource_as } = locreq("test_utils");
+const { with_stopped_app, with_running_app } = locreq(
+ "test_utils/with-test-app.js"
+);
+const DatastoreMongoFactory = locreq("lib/datastore/db.js");
+
+describe("reverse-single-reference", () => {
+ async function create_referencing_collections(app, with_reverse) {
+ const A = app.createChip(app.Sealious.Collection, {
+ name: "A",
+ fields: [
+ {
+ name: "reference_to_b",
+ type: "single_reference",
+ params: { collection: "B" },
+ },
+ {
+ name: "pairity",
+ type: "text",
+ },
+ ],
+ });
+ const B = app.createChip(app.Sealious.Collection, {
+ name: "B",
+ fields: [{ name: "number", type: "int" }],
+ });
+ if (with_reverse) {
+ B.add_field({
+ name: "references_in_a",
+ type: "reverse-single-reference",
+ params: { collection: A, field_name: "reference_to_b" },
+ });
+ }
+ }
+
+ async function create_resources(app) {
+ const numbers = [1, 2, 3];
+ const bs = await Promise.map(numbers, number =>
+ app.run_action(
+ new app.Sealious.SuperContext(),
+ ["collections", "B"],
+ "create",
+ { number }
+ )
+ );
+ for (let b of bs) {
+ for (let i = 1; i <= b.body.number; i++) {
+ await app.run_action(
+ new app.Sealious.SuperContext(),
+ ["collections", "A"],
+ "create",
+ { reference_to_b: b.id, pairity: i % 2 ? "odd" : "even" }
+ );
+ }
+ }
+ }
+
+ async function with_reverse(fn) {
+ return with_stopped_app(async args => {
+ await create_referencing_collections(args.app, "with_reverse" && true);
+ await args.app.start();
+ await create_resources(args.app);
+ await fn(args);
+ });
+ }
+
+ it("recreates the cached values if the field has just been added", async () => {
+ await with_stopped_app(async ({ app, dont_clear_database_on_stop }) => {
+ await create_referencing_collections(app, "with_reverse" && false);
+ await app.start();
+ await create_resources(app);
+ dont_clear_database_on_stop();
+ });
+ await with_stopped_app(async ({ base_url, app, rest_api }) => {
+ await create_referencing_collections(app, "with_reverse" && true);
+ await app.start();
+ const result = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=1"
+ ))[0];
+ assert(result.body.references_in_a);
+ assert.equal(result.body.references_in_a.length, 1);
+ const result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ assert(result2.body.references_in_a);
+ assert.equal(result2.body.references_in_a.length, 2);
+ });
+ });
+
+ it("updates the cached value when a new reference is created", async () => {
+ await with_stopped_app(async ({ app, rest_api }) => {
+ await create_referencing_collections(app, "with_reverse" && true);
+ await app.start();
+ await create_resources(app);
+ const result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ assert(result2.body.references_in_a instanceof Array);
+ assert.equal(result2.body.references_in_a.length, 2);
+ });
+ });
+
+ it("updates the cached value when an old reference is deleted", async () =>
+ with_reverse(async ({ app, rest_api }) => {
+ const result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ const referencing_id = result2.body.references_in_a[0];
+ await rest_api.delete(`/api/v1/collections/A/${referencing_id}`);
+ const new_result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ assert.equal(new_result2.body.references_in_a.length, 1);
+ }));
+
+ it("updates the cached value when an old reference is edited to a new one", async () =>
+ with_reverse(async ({ app, rest_api }) => {
+ const result1 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=1"
+ ))[0];
+ const result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ const referencing_id = result2.body.references_in_a[0];
+
+ await rest_api.patch(`/api/v1/collections/A/${referencing_id}`, {
+ reference_to_b: result1.id,
+ });
+ const new_result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ assert.equal(new_result2.body.references_in_a.length, 1);
+ const new_result1 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=1"
+ ))[0];
+ assert.equal(new_result1.body.references_in_a.length, 2);
+ }));
+
+ it("updates the cached value when an old reference is edited to an empty one", async () =>
+ with_reverse(async ({ app, rest_api }) => {
+ const result1 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=1"
+ ))[0];
+ const result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ const referencing_id = result2.body.references_in_a[0];
+
+ await rest_api.patch(`/api/v1/collections/A/${referencing_id}`, {
+ reference_to_b: "",
+ });
+ const new_result2 = (await rest_api.get(
+ "/api/v1/collections/B?filter[number]=2"
+ ))[0];
+ assert.equal(new_result2.body.references_in_a.length, 1);
+ }));
+
+ it("allows to filter by a value of the referencing resource", async () =>
+ with_reverse(async ({ app, rest_api }) => {
+ let results = await rest_api.get(
+ "/api/v1/collections/B?filter[references_in_a][pairity]=non-existant"
+ );
+ assert.equal(results.length, 0);
+ results = await rest_api.get(
+ "/api/v1/collections/B?filter[references_in_a][pairity]=odd"
+ );
+ assert.equal(results.length, 3);
+ results = await rest_api.get(
+ "/api/v1/collections/B?filter[references_in_a][pairity]=even&filter[number]=3"
+ );
+ assert.equal(results.length, 1);
+ }));
+
+ it("allows to display the full body of the referencing resources", async () =>
+ with_reverse(async ({ app, rest_api }) => {
+ let results = await rest_api.get(
+ "/api/v1/collections/B?format[references_in_a]=expand"
+ );
+ assert(results[0].body.references_in_a[0].body);
+ }));
+});
diff --git a/lib/app/base-chips/field-types/single_reference.subtest.js b/lib/app/base-chips/field-types/single_reference.subtest.js
--- a/lib/app/base-chips/field-types/single_reference.subtest.js
+++ b/lib/app/base-chips/field-types/single_reference.subtest.js
@@ -2,7 +2,7 @@
const locreq = require("locreq")(__dirname);
const axios = require("axios");
const { create_resource_as } = locreq("test_utils");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("single_reference", () => {
async function create_referencing_collections(app) {
@@ -28,7 +28,7 @@
}
it("should not allow a value that is not an existing id", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
await create_referencing_collections(app);
return axios
.post(`${base_url}/api/v1/collections/A`, {
@@ -46,7 +46,7 @@
}));
it("should allow a value that exists in B", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
create_referencing_collections(app);
const b_id = (await axios.post(`${base_url}/api/v1/collections/B`, {
number: 1,
@@ -57,7 +57,7 @@
}));
it("should not allow a value that exists in B but does not meet the filter criteria", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
create_referencing_collections(app);
const b_id = (await axios.post(`${base_url}/api/v1/collections/B`, {
number: 0,
@@ -79,7 +79,7 @@
}));
it("should allow a value that exists in B but does not meet the filter criteria", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
create_referencing_collections(app);
const b_id = (await axios.post(`${base_url}/api/v1/collections/B`, {
number: 1,
diff --git a/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js b/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js
--- a/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js
+++ b/lib/app/base-chips/special_filters/IsReferencedByResourcesMatching.subtest.js
@@ -5,7 +5,7 @@
const { create_resource_as } = locreq("test_utils");
const IsReferencedByResourcesMatching = require("./IsReferencedByResourcesMatching");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("IsReferencedByResourcesMatching", () => {
async function setup(app) {
@@ -86,7 +86,7 @@
}
it("returns only users with role matching `allowed_values`", () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
await setup(app);
return axios
.get(`${base_url}/api/v1/collections/users/@staff`)
diff --git a/lib/app/base-chips/special_filters/matches.subtest.js b/lib/app/base-chips/special_filters/matches.subtest.js
--- a/lib/app/base-chips/special_filters/matches.subtest.js
+++ b/lib/app/base-chips/special_filters/matches.subtest.js
@@ -5,7 +5,7 @@
const { create_resource_as } = locreq("test_utils");
const matches = require("./matches");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("Matches", () => {
async function setup(app) {
@@ -35,7 +35,7 @@
}
it("returns only positive numbers when using @positive filter", () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
await setup(app);
return axios
.get(
@@ -50,7 +50,7 @@
}));
it("returns empty array when using both @positive and @negative filters", () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
await setup(app);
return axios
.get(`${base_url}/api/v1/collections/numbers/@positive/@negative`)
diff --git a/lib/app/load-base-chips.js b/lib/app/load-base-chips.js
--- a/lib/app/load-base-chips.js
+++ b/lib/app/load-base-chips.js
@@ -45,6 +45,7 @@
"value-existing-in-collection",
"value-not-existing-in-collection",
"secret-token",
+ "reverse-single-reference",
]);
BaseChips.set(CalculatedFieldType, ["map-reduce", "aggregate", "custom"]);
diff --git a/lib/app/metadata.js b/lib/app/metadata.js
new file mode 100644
--- /dev/null
+++ b/lib/app/metadata.js
@@ -0,0 +1,25 @@
+const COLLECTION_NAME = "_metadata";
+
+module.exports = app => ({
+ db_collection_name: COLLECTION_NAME,
+ async get(key) {
+ const matches = await app.Datastore.find(COLLECTION_NAME, { key });
+ if (matches.length) {
+ return matches[0].value;
+ } else {
+ undefined;
+ }
+ },
+ async set(key, value) {
+ const matches = await app.Datastore.find(COLLECTION_NAME, { key });
+ if (matches.length) {
+ await app.Datastore.update(
+ COLLECTION_NAME,
+ { key: key },
+ { $set: { value: value } }
+ );
+ } else {
+ await app.Datastore.insert(COLLECTION_NAME, { key, value });
+ }
+ },
+});
diff --git a/lib/chip-types/collection.js b/lib/chip-types/collection.js
--- a/lib/chip-types/collection.js
+++ b/lib/chip-types/collection.js
@@ -73,8 +73,13 @@
Collection.type_name = "collection";
Collection.pure = {
- add_field: function(app, field_type, fields, field_declaration) {
- const field_object = new Field(app, field_declaration, field_type);
+ add_field: function(app, field_type, fields, field_declaration, collection) {
+ const field_object = new Field(
+ app,
+ field_declaration,
+ field_type,
+ collection
+ );
const field_name = field_object.name;
if (!fields[field_name]) {
fields[field_name] = field_object;
@@ -521,7 +526,7 @@
Collection.prototype = {
add_field(field_declaration) {
- return pure.add_field(this.app, this, this.fields, field_declaration);
+ return pure.add_field(this.app, this, this.fields, field_declaration, this);
},
add_fields(field_declarations_array) {
return pure.add_fields(
diff --git a/lib/chip-types/field-type-default-methods.js b/lib/chip-types/field-type-default-methods.js
--- a/lib/chip-types/field-type-default-methods.js
+++ b/lib/chip-types/field-type-default-methods.js
@@ -5,67 +5,73 @@
const FieldTypeDescription = require("../data-structures/field-type-description.js");
const default_methods = {
- has_index: function(params){
+ init: function() {
+ return null;
+ },
+ has_index: function(params) {
return false;
},
- is_proper_value: function(context, params, new_value, old_value){
+ is_proper_value: function(context, params, new_value, old_value) {
return Promise.resolve();
},
- format: function(context, params, decoded_value, format_params){
+ format: function(context, params, decoded_value, format_params) {
return decoded_value;
},
- encode: function(context, params, value_in_code){
+ encode: function(context, params, value_in_code) {
return value_in_code;
},
- get_description: function(context, params){
+ get_description: function(context, params) {
return new FieldTypeDescription(this.name);
},
- decode: function(context, params, value_in_database){
+ decode: function(context, params, value_in_database) {
return value_in_database;
},
- filter_to_query: function(context, params, query){
- return Promise.resolve(this.encode(context, params, query))
- .then(function(encoded_value){
+ filter_to_query: function(context, params, query) {
+ return Promise.resolve(this.encode(context, params, query)).then(function(
+ encoded_value
+ ) {
return {
$eq: encoded_value,
};
});
},
- full_text_search_enabled: function(){
+ full_text_search_enabled: function() {
return false;
},
- get_aggregation_stages: function(context, params, field_name, query_params){
+ get_aggregation_stages: function(context, params, field_name, query_params) {
const self = this;
- if(!query_params || !query_params.filter) return Promise.resolve([]);
+ if (!query_params || !query_params.filter) return Promise.resolve([]);
const expanded_filter = expandHash(query_params.filter);
let field_filter = expanded_filter[field_name];
- if(field_filter && field_filter.length === 1 && field_filter[0] instanceof Array){
+ if (
+ field_filter &&
+ field_filter.length === 1 &&
+ field_filter[0] instanceof Array
+ ) {
field_filter = field_filter[0]; // to fix an edge case where instead of array of values the array is wrapped within another array
}
- if(!(field_name in expanded_filter)){
+ if (!(field_name in expanded_filter)) {
return Promise.resolve([]);
}
- if(field_name in expanded_filter && field_filter === undefined)
- return Promise.resolve([{$match: {[`body.${field_name}`]: {$exists: false}}}]);
+ if (field_name in expanded_filter && field_filter === undefined)
+ return Promise.resolve([
+ { $match: { [`body.${field_name}`]: { $exists: false } } },
+ ]);
let new_filter = null;
- if(field_filter instanceof Array){
+ if (field_filter instanceof Array) {
new_filter = Promise.all(
- field_filter.map(function(element){
+ field_filter.map(function(element) {
return self.encode(context, params, element);
})
- )
- .then((filters)=> {
- return {$in: filters};
+ ).then(filters => {
+ return { $in: filters };
});
- }else{
+ } else {
new_filter = self.filter_to_query(context, params, field_filter);
}
- return new_filter
- .then(function(filter){
- return [
- {$match: {[`body.${field_name}`]: filter}},
- ];
- });
+ return new_filter.then(function(filter) {
+ return [{ $match: { [`body.${field_name}`]: filter } }];
+ });
},
};
diff --git a/lib/chip-types/field.js b/lib/chip-types/field.js
--- a/lib/chip-types/field.js
+++ b/lib/chip-types/field.js
@@ -3,27 +3,29 @@
const default_methods = require("./field-type-default-methods.js");
const FieldType = locreq("lib/chip-types/field-type.js");
-function Field (app, declaration){
-
+function Field(app, declaration, collection) {
this.name = declaration.name;
this.declaration = declaration;
this.type = new FieldType(app, declaration.type);
this.required = declaration.required || false;
this.params = declaration.params || {};
+ this.type.init(collection, declaration.name, this.params);
const self = this;
- for (const method_name in default_methods){
- this[method_name] = (function(method_name){
- return function(){
- const arguments_array = Object.keys(arguments).map((key)=>arguments[key]);
+ for (const method_name in default_methods) {
+ this[method_name] = (function(method_name) {
+ return function() {
+ const arguments_array = Object.keys(arguments).map(
+ key => arguments[key]
+ );
arguments_array.splice(1, 0, self.params);
return self.type[method_name].apply(self.type, arguments_array);
};
})(method_name);
}
- this.get_specification = function(){
+ this.get_specification = function() {
return {
name: this.name,
type: this.type,
@@ -31,14 +33,17 @@
};
};
- this.get_aggregation_stages = function(context, query_params){
+ this.get_aggregation_stages = function(context, query_params) {
const self = this;
return Promise.resolve(
- self.type.get_aggregation_stages(context, self.params, self.name, query_params)
+ self.type.get_aggregation_stages(
+ context,
+ self.params,
+ self.name,
+ query_params
+ )
);
-
};
-
}
module.exports = Field;
diff --git a/lib/email/templates/simple.test.js b/lib/email/templates/simple.test.js
--- a/lib/email/templates/simple.test.js
+++ b/lib/email/templates/simple.test.js
@@ -1,10 +1,10 @@
const locreq = require("locreq")(__dirname);
const assert = require("assert");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("simpleTemplate", () => {
it("sends an email", async () =>
- with_test_app(async ({ app, mail_api }) => {
+ with_running_app(async ({ app, mail_api }) => {
const message = await app.EmailTemplates.Simple(app, {
to: "test@example.com",
subject: "Congratulations!",
diff --git a/lib/http/routes/confirm-password-reset.test.js b/lib/http/routes/confirm-password-reset.test.js
--- a/lib/http/routes/confirm-password-reset.test.js
+++ b/lib/http/routes/confirm-password-reset.test.js
@@ -1,11 +1,11 @@
const locreq = require("locreq")(__dirname);
const axios = require("axios");
const assert = require("assert");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("confirm-password-reset", () => {
it("displays an html form", async () =>
- with_test_app(async ({ app, base_url }) => {
+ with_running_app(async ({ app, base_url }) => {
const response = await axios.get(
`${base_url}/confirm-password-reset?token=kupcia&email=dupcia`
);
diff --git a/lib/http/routes/finalize-password-reset.test.js b/lib/http/routes/finalize-password-reset.test.js
--- a/lib/http/routes/finalize-password-reset.test.js
+++ b/lib/http/routes/finalize-password-reset.test.js
@@ -3,7 +3,7 @@
const axios = require("axios");
const tough = require("tough-cookie");
const { promise_timeout, assert_throws_async } = locreq("test_utils");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("finalize password reset", () => {
async function create_a_user(app) {
@@ -20,7 +20,7 @@
}
it("allows to change a password (entire flow)", async () =>
- with_test_app(async ({ app, base_url, mail_api }) => {
+ with_running_app(async ({ app, base_url, mail_api }) => {
await create_a_user(app);
const cookieJar = new tough.CookieJar();
const options = {
diff --git a/lib/http/routes/finalize-registration-intent.test.js b/lib/http/routes/finalize-registration-intent.test.js
--- a/lib/http/routes/finalize-registration-intent.test.js
+++ b/lib/http/routes/finalize-registration-intent.test.js
@@ -3,11 +3,11 @@
const axios = require("axios");
const tough = require("tough-cookie");
const { promise_timeout, assert_throws_async } = locreq("test_utils");
-const with_test_app = locreq("test_utils/with-test-app.js");
+const { with_running_app } = locreq("test_utils/with-test-app.js");
describe("finalize registration", () => {
it("allows to register an account (entire flow)", async () =>
- with_test_app(async ({ app, base_url, mail_api }) => {
+ with_running_app(async ({ app, base_url, mail_api }) => {
const cookieJar = new tough.CookieJar();
const options = {
jar: cookieJar,
diff --git a/test_utils/with-test-app.js b/test_utils/with-test-app.js
--- a/test_utils/with-test-app.js
+++ b/test_utils/with-test-app.js
@@ -1,7 +1,12 @@
const locreq = require("locreq")(__dirname);
const axios = require("axios");
-module.exports = async function with_test_app(fn) {
+module.exports = {
+ with_stopped_app: with_test_app.bind(global, "auto_start" && false),
+ with_running_app: with_test_app.bind(global, "auto_start" && true),
+};
+
+async function with_test_app(auto_start, fn) {
let app = null;
const port = 8888;
const base_url = `http://localhost:${port}`;
@@ -41,15 +46,26 @@
}
);
- app.on("stop", async () =>
- Promise.all(
- app.ChipManager.get_all_collections().map(collection_name =>
- app.Datastore.remove(collection_name, {}, "just_one" && false)
- )
- )
- );
+ let clear_database_on_stop = true;
+
+ app.on("stop", async () => {
+ if (clear_database_on_stop) {
+ await Promise.all(
+ app.ChipManager.get_all_collections().map(collection_name =>
+ app.Datastore.remove(collection_name, {}, "just_one" && false)
+ )
+ );
+ await app.Datastore.remove(
+ app.Metadata.db_collection_name,
+ {},
+ "just_one" && false
+ );
+ }
+ });
- await app.start();
+ if (auto_start) {
+ await app.start();
+ }
try {
await axios.delete(`${smtp_api_url}/messages`);
@@ -64,11 +80,19 @@
get_message_by_id: async id =>
(await axios.get(`${smtp_api_url}/messages/${id}.html`)).data,
},
+ dont_clear_database_on_stop: () => (clear_database_on_stop = false),
+ rest_api: {
+ get: async url => (await axios.get(`${base_url}${url}`)).data,
+ delete: async url => (await axios.delete(`${base_url}${url}`)).data,
+ patch: async (url, data) =>
+ (await axios.patch(`${base_url}${url}`, data)).data,
+ },
});
-
- return await app.stop();
} catch (e) {
- await app.stop();
throw e;
+ } finally {
+ if (app.status !== "stopped") {
+ await app.stop();
+ }
}
-};
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 23, 09:19 (16 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
557066
Default Alt Text
D200.id700.diff (33 KB)

Event Timeline