2016-01-08 08:31:43 +00:00
|
|
|
describe("Zotero.Schema", function() {
|
2016-02-29 09:23:00 +00:00
|
|
|
describe("#initializeSchema()", function () {
|
|
|
|
it("should set last client version", function* () {
|
|
|
|
yield resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
|
|
|
|
var sql = "SELECT value FROM settings WHERE setting='client' AND key='lastVersion'";
|
|
|
|
var lastVersion = yield Zotero.DB.valueQueryAsync(sql);
|
|
|
|
yield assert.eventually.equal(Zotero.DB.valueQueryAsync(sql), Zotero.version);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("#updateSchema()", function () {
|
|
|
|
it("should set last client version", function* () {
|
|
|
|
var sql = "REPLACE INTO settings (setting, key, value) VALUES ('client', 'lastVersion', ?)";
|
2018-09-26 06:50:05 +00:00
|
|
|
yield Zotero.DB.queryAsync(sql, "5.0old");
|
2016-02-29 09:23:00 +00:00
|
|
|
|
|
|
|
yield Zotero.Schema.updateSchema();
|
|
|
|
|
|
|
|
var sql = "SELECT value FROM settings WHERE setting='client' AND key='lastVersion'";
|
|
|
|
var lastVersion = yield Zotero.DB.valueQueryAsync(sql);
|
|
|
|
yield assert.eventually.equal(Zotero.DB.valueQueryAsync(sql), Zotero.version);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
describe("Global Schema", function () {
|
|
|
|
var schemaJSON, schema;
|
|
|
|
|
|
|
|
before(async function () {
|
|
|
|
schemaJSON = await Zotero.File.getResourceAsync('resource://zotero/schema/global/schema.json');
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(async function () {
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
schema = JSON.parse(schemaJSON);
|
|
|
|
});
|
|
|
|
|
|
|
|
after(async function() {
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("#migrateExtraFields()", function () {
|
2020-03-19 19:59:50 +00:00
|
|
|
async function migrate() {
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
schema.version++;
|
|
|
|
schema.itemTypes.find(x => x.itemType == 'book').fields.splice(0, 1, { field: 'fooBar' })
|
|
|
|
var newLocales = {};
|
|
|
|
Object.keys(schema.locales).forEach((locale) => {
|
|
|
|
var o = schema.locales[locale];
|
|
|
|
o.fields.fooBar = 'Foo Bar';
|
|
|
|
newLocales[locale] = o;
|
|
|
|
});
|
|
|
|
await Zotero.Schema._updateGlobalSchemaForTest(schema);
|
|
|
|
await Zotero.Schema.migrateExtraFields();
|
2020-03-19 19:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
it("should add a new field and migrate values from Extra", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setField('numPages', "10");
|
|
|
|
item.setField('extra', 'Foo Bar: This is a value.\nnumber-of-pages: 11\nThis is another line.');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
|
|
|
assert.isNumber(Zotero.ItemFields.getID('fooBar'));
|
|
|
|
assert.equal(Zotero.ItemFields.getLocalizedString('fooBar'), 'Foo Bar');
|
|
|
|
assert.equal(item.getField('fooBar'), 'This is a value.');
|
|
|
|
// Existing fields shouldn't be overwritten and should be left in Extra
|
|
|
|
assert.equal(item.getField('numPages'), '10');
|
|
|
|
assert.equal(item.getField('extra'), 'number-of-pages: 11\nThis is another line.');
|
2019-09-28 11:39:52 +00:00
|
|
|
assert.isFalse(item.synced);
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
});
|
2020-03-15 17:51:04 +00:00
|
|
|
|
2020-03-17 14:37:16 +00:00
|
|
|
it("should migrate valid creator", async function () {
|
2020-03-15 17:51:04 +00:00
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setCreators([
|
|
|
|
{
|
|
|
|
firstName: 'Abc',
|
|
|
|
lastName: 'Def',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
item.setField('extra', 'editor: Last || First\nFoo: Bar');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-15 17:51:04 +00:00
|
|
|
|
|
|
|
var creators = item.getCreators();
|
|
|
|
assert.lengthOf(creators, 2);
|
|
|
|
assert.propertyVal(creators[0], 'firstName', 'Abc');
|
|
|
|
assert.propertyVal(creators[0], 'lastName', 'Def');
|
|
|
|
assert.propertyVal(creators[0], 'creatorTypeID', Zotero.CreatorTypes.getID('author'));
|
|
|
|
assert.propertyVal(creators[1], 'firstName', 'First');
|
|
|
|
assert.propertyVal(creators[1], 'lastName', 'Last');
|
|
|
|
assert.propertyVal(creators[1], 'creatorTypeID', Zotero.CreatorTypes.getID('editor'));
|
|
|
|
assert.equal(item.getField('extra'), 'Foo: Bar');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
2020-03-15 23:52:44 +00:00
|
|
|
|
2020-03-17 14:37:16 +00:00
|
|
|
it("shouldn't migrate creator not valid for item type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setCreators([
|
|
|
|
{
|
|
|
|
firstName: 'Abc',
|
|
|
|
lastName: 'Def',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
item.setField('extra', 'container-author: Last || First\nFoo: Bar');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-17 14:37:16 +00:00
|
|
|
|
|
|
|
var creators = item.getCreators();
|
|
|
|
assert.lengthOf(creators, 1);
|
|
|
|
assert.propertyVal(creators[0], 'firstName', 'Abc');
|
|
|
|
assert.propertyVal(creators[0], 'lastName', 'Def');
|
|
|
|
assert.propertyVal(creators[0], 'creatorTypeID', Zotero.CreatorTypes.getID('author'));
|
|
|
|
assert.equal(item.getField('extra'), 'container-author: Last || First\nFoo: Bar');
|
|
|
|
assert.isTrue(item.synced);
|
|
|
|
});
|
|
|
|
|
2020-03-15 23:52:44 +00:00
|
|
|
it("shouldn't migrate fields in read-only library", async function () {
|
|
|
|
var library = await createGroup({ editable: false, filesEditable: false });
|
|
|
|
var item = createUnsavedDataObject('item', { libraryID: library.libraryID, itemType: 'book' });
|
|
|
|
item.setField('extra', 'Foo Bar: This is a value.');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx({
|
|
|
|
skipEditCheck: true
|
|
|
|
});
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-15 23:52:44 +00:00
|
|
|
|
|
|
|
assert.isNumber(Zotero.ItemFields.getID('fooBar'));
|
|
|
|
assert.equal(item.getField('fooBar'), '');
|
|
|
|
assert.equal(item.getField('extra'), 'Foo Bar: This is a value.');
|
|
|
|
assert.isTrue(item.synced);
|
|
|
|
});
|
2020-03-17 17:51:19 +00:00
|
|
|
|
2020-03-22 19:19:24 +00:00
|
|
|
it("should change item type if 'type:' is defined", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'document' });
|
|
|
|
item.setField('extra', 'type: personal_communication');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('letter'));
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should remove 'type:' line for CSL type if item is the first mapped Zotero type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'letter' });
|
|
|
|
item.setField('extra', 'type: personal_communication');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('letter'));
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should remove 'type:' line for CSL type if item is a non-primary mapped Zotero type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'instantMessage' });
|
|
|
|
item.setField('extra', 'type: personal_communication');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('instantMessage'));
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should move existing fields that would be invalid in the new 'type:' type to Extra", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setField('numPages', '123');
|
|
|
|
item.setField('extra', 'type: article-journal\nJournal Abbreviation: abc.\nnumPages: 234');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('journalArticle'));
|
|
|
|
assert.equal(item.getField('journalAbbreviation'), 'abc.');
|
|
|
|
// Migrated real field should be placed at beginning, followed by unused line from Extra
|
|
|
|
assert.equal(item.getField('extra'), 'Num Pages: 123\nnumPages: 234');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
2020-03-17 17:51:19 +00:00
|
|
|
it("shouldn't migrate invalid item type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setField('numPages', 30);
|
|
|
|
item.setCreators(
|
|
|
|
[
|
|
|
|
{
|
|
|
|
firstName: 'Abc',
|
|
|
|
lastName: 'Def',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
},
|
|
|
|
{
|
|
|
|
firstName: 'Ghi',
|
|
|
|
lastName: 'Jkl',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
item.setField('extra', 'type: invalid');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-17 17:51:19 +00:00
|
|
|
|
|
|
|
assert.equal(item.getField('numPages'), 30);
|
|
|
|
var creators = item.getCreators();
|
|
|
|
assert.lengthOf(creators, 2);
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('book'));
|
2020-03-17 18:17:02 +00:00
|
|
|
assert.equal(item.getField('extra'), 'type: invalid');
|
2020-03-17 17:51:19 +00:00
|
|
|
assert.isTrue(item.synced);
|
|
|
|
});
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2016-01-08 08:31:43 +00:00
|
|
|
describe("#integrityCheck()", function () {
|
|
|
|
before(function* () {
|
|
|
|
yield resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
})
|
|
|
|
|
2021-01-17 08:30:00 +00:00
|
|
|
it("should create missing tables unless 'skipReconcile' is true", async function () {
|
2020-11-16 23:01:16 +00:00
|
|
|
await Zotero.DB.queryAsync("DROP TABLE retractedItems");
|
|
|
|
assert.isFalse(await Zotero.DB.tableExists('retractedItems'));
|
2021-01-17 08:30:00 +00:00
|
|
|
assert.isTrue(await Zotero.Schema.integrityCheck(false, { skipReconcile: true }));
|
|
|
|
|
2020-11-16 23:01:16 +00:00
|
|
|
assert.isFalse(await Zotero.Schema.integrityCheck());
|
|
|
|
assert.isTrue(await Zotero.Schema.integrityCheck(true));
|
|
|
|
assert.isTrue(await Zotero.DB.tableExists('retractedItems'));
|
|
|
|
});
|
|
|
|
|
2016-01-08 08:31:43 +00:00
|
|
|
it("should repair a foreign key violation", function* () {
|
|
|
|
yield assert.eventually.isTrue(Zotero.Schema.integrityCheck());
|
|
|
|
|
|
|
|
yield Zotero.DB.queryAsync("PRAGMA foreign_keys = OFF");
|
|
|
|
yield Zotero.DB.queryAsync("INSERT INTO itemTags VALUES (1234,1234,0)");
|
|
|
|
yield Zotero.DB.queryAsync("PRAGMA foreign_keys = ON");
|
|
|
|
|
|
|
|
yield assert.eventually.isFalse(Zotero.Schema.integrityCheck());
|
|
|
|
yield assert.eventually.isTrue(Zotero.Schema.integrityCheck(true));
|
|
|
|
yield assert.eventually.isTrue(Zotero.Schema.integrityCheck());
|
|
|
|
})
|
2020-03-13 22:04:04 +00:00
|
|
|
|
|
|
|
it("should repair invalid nesting between two collections", async function () {
|
|
|
|
var c1 = await createDataObject('collection');
|
|
|
|
var c2 = await createDataObject('collection', { parentID: c1.id });
|
|
|
|
await Zotero.DB.queryAsync(
|
|
|
|
"UPDATE collections SET parentCollectionID=? WHERE collectionID=?",
|
|
|
|
[c2.id, c1.id]
|
|
|
|
);
|
|
|
|
|
|
|
|
await assert.isFalse(await Zotero.Schema.integrityCheck());
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck(true));
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck());
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should repair invalid nesting between three collections", async function () {
|
|
|
|
var c1 = await createDataObject('collection');
|
|
|
|
var c2 = await createDataObject('collection', { parentID: c1.id });
|
|
|
|
var c3 = await createDataObject('collection', { parentID: c2.id });
|
|
|
|
await Zotero.DB.queryAsync(
|
|
|
|
"UPDATE collections SET parentCollectionID=? WHERE collectionID=?",
|
|
|
|
[c3.id, c2.id]
|
|
|
|
);
|
|
|
|
|
|
|
|
await assert.isFalse(await Zotero.Schema.integrityCheck());
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck(true));
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck());
|
|
|
|
});
|
2016-01-08 08:31:43 +00:00
|
|
|
})
|
|
|
|
})
|