Skip to content

Commit

Permalink
Add tests to check for V1CheckpointManager fixing dbGuid if mis-match. (
Browse files Browse the repository at this point in the history
#1064)

* Add tests to check for V1CheckpointManager fixing dbGuid if mis-match.
  • Loading branch information
calebmshafer committed Mar 30, 2021
1 parent b4da068 commit ebf11e0
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 22 deletions.
2 changes: 1 addition & 1 deletion common/api/imodeljs-backend.api.md
Expand Up @@ -1341,7 +1341,7 @@ export interface DownloadJob {
status: DownloadBriefcaseStatus;
}

// @beta
// @internal
export interface DownloadRequest {
aliasFiles?: string[];
checkpoint: CheckpointProps;
Expand Down
2 changes: 1 addition & 1 deletion common/api/summary/imodeljs-backend.exports.csv
Expand Up @@ -81,7 +81,7 @@ deprecated;class DocumentCarrier
public;DocumentListModel
public;DocumentPartition
internal;DownloadJob
beta;DownloadRequest
internal;DownloadRequest
internal;Downloads
public;Drawing
public;DrawingCategory
Expand Down
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@bentley/imodeljs-backend",
"comment": "",
"type": "none"
}
],
"packageName": "@bentley/imodeljs-backend",
"email": "31107829+calebmshafer@users.noreply.github.com"
}
2 changes: 1 addition & 1 deletion core/backend/src/CheckpointManager.ts
Expand Up @@ -50,7 +50,7 @@ export interface CheckpointProps {
export type ProgressFunction = (loaded: number, total: number) => number;

/** The parameters that specify a request to download a checkpoint file from iModelHub.
* @beta
* @internal
*/
export interface DownloadRequest {
/** name of local file to hold the downloaded data. */
Expand Down
8 changes: 8 additions & 0 deletions core/backend/src/test/IModelTestUtils.ts
Expand Up @@ -262,6 +262,14 @@ export class IModelTestUtils {
}
}

public static generateChangeSetId(): string {
let result = "";
for (let i = 0; i < 20; ++i) {
result += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
}
return result;
}

/** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
public static createAndInsertPhysicalPartition(testDb: IModelDb, newModelCode: CodeProps, parentId?: Id64String): Id64String {
const model = parentId ? testDb.elements.getElement(parentId).model : IModel.repositoryModelId;
Expand Down
5 changes: 2 additions & 3 deletions core/backend/src/test/integration/Agent.test.ts
Expand Up @@ -13,8 +13,7 @@ import { HubUtility } from "./HubUtility";
// imjs_agent_test_client_id
// imjs_agent_test_client_secret

describe("Agent (#integration)", () => {
// iOS does not support agent test
describe("Agent iModel Download (#integration)", () => {
let testProjectId: string;
let testReadIModelId: string;
let testWriteIModelId: string;
Expand All @@ -26,7 +25,7 @@ describe("Agent (#integration)", () => {
const agentConfiguration: AgentAuthorizationClientConfiguration = {
clientId: Config.App.getString("imjs_agent_test_client_id"),
clientSecret: Config.App.getString("imjs_agent_test_client_secret"),
scope: "imodelhub rbac-user:external-client reality-data:read urlps-third-party context-registry-service:read-only imodeljs-backend-2686",
scope: "imodelhub context-registry-service:read-only",
};

const agentClient = new AgentAuthorizationClient(agentConfiguration);
Expand Down
4 changes: 1 addition & 3 deletions core/backend/src/test/standalone/Category.test.ts
Expand Up @@ -10,8 +10,6 @@ import {
} from "../../imodeljs-backend";
import { IModelTestUtils } from "../IModelTestUtils";

// spell-checker: disable

describe("Category", () => {
let imodel: StandaloneDb;
before(() => {
Expand Down Expand Up @@ -42,7 +40,7 @@ describe("Category", () => {
const materialId = RenderMaterialElement.insert(imodel, IModelDb.dictionaryId, "FieldWeldMaterial", params);
expect(Id64.isValidId64(materialId)).to.be.true;

const appearance = new SubCategoryAppearance({material: materialId, priority: 100, transp: 0.75});
const appearance = new SubCategoryAppearance({ material: materialId, priority: 100, transp: 0.75 });
const priCategoryId = SpatialCategory.insert(imodel, IModelDb.dictionaryId, "FieldWeld", appearance);
expect(Id64.isValidId64(priCategoryId)).to.be.true;

Expand Down
152 changes: 152 additions & 0 deletions core/backend/src/test/standalone/CheckpointManager.test.ts
@@ -0,0 +1,152 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { assert } from "chai";
import * as path from "path";
import * as sinon from "sinon";
import { ClientRequestContext, Guid } from "@bentley/bentleyjs-core";
import { AccessToken, AuthorizedClientRequestContext } from "@bentley/itwin-client";
import { CheckpointManager, V1CheckpointManager } from "../../CheckpointManager";
import { IModelHost } from "../../imodeljs-backend";
import { SnapshotDb } from "../../IModelDb";
import { IModelJsFs } from "../../IModelJsFs";
import { IModelTestUtils } from "../IModelTestUtils";

describe("V1 Checkpoint Manager", () => {
it("empty props", async () => {
const props = {
contextId: "",
iModelId: "",
changeSetId: "",
requestContext: new AuthorizedClientRequestContext(new AccessToken()),
};
assert.equal(V1CheckpointManager.getFileName(props), path.join(IModelHost.cacheDir, "imodels", "checkpoints", "first.bim"));
});

it("changeset only props", async () => {
const props = {
contextId: "",
iModelId: "",
changeSetId: "1234",
requestContext: new AuthorizedClientRequestContext(new AccessToken()),
};
assert.equal(V1CheckpointManager.getFileName(props), path.join(IModelHost.cacheDir, "imodels", "checkpoints", "1234.bim"));
});

it("changeset+context props", async () => {
const props = {
contextId: "5678",
iModelId: "",
changeSetId: "1234",
requestContext: new AuthorizedClientRequestContext(new AccessToken()),
};
assert.equal(V1CheckpointManager.getFileName(props), path.join(IModelHost.cacheDir, "imodels", "checkpoints", "1234.bim"));
});

it("changeset+context+imodel props", async () => {
const props = {
contextId: "5678",
iModelId: "910",
changeSetId: "1234",
requestContext: new AuthorizedClientRequestContext(new AccessToken()),
};
assert.equal(V1CheckpointManager.getFileName(props), path.join(IModelHost.cacheDir, "imodels", "910", "checkpoints", "1234.bim"));
});

it("getFolder", async () => {
assert.equal(V1CheckpointManager.getFolder("1234"), path.join(IModelHost.cacheDir, "imodels", "1234", "checkpoints"));
assert.equal(V1CheckpointManager.getFolder(""), path.join(IModelHost.cacheDir, "imodels", "checkpoints"));
});

it("should fix invalid dbGuid during download", async () => {
const dbPath = IModelTestUtils.prepareOutputFile("IModel", "TestCheckpoint.bim");
const snapshot = SnapshotDb.createEmpty(dbPath, { rootSubject: { name: "test" } });
const iModelId = Guid.createValue(); // This is wrong - it should be `snapshot.getGuid()`!
const contextId = Guid.createValue();
const changeSetId = IModelTestUtils.generateChangeSetId();
snapshot.nativeDb.saveProjectGuid(Guid.normalize(contextId));
snapshot.nativeDb.saveLocalValue("ParentChangeSetId", changeSetId);
snapshot.saveChanges();

assert.notEqual(iModelId, snapshot.nativeDb.getDbGuid()); // Ensure the Snapshot dbGuid and iModelId are different

snapshot.close();

const mockCheckpoint = {
wsgId: "INVALID",
ecId: "INVALID",
changeSetId,
downloadUrl: `INVALID`,
mergedChangeSetId: changeSetId,
};

const checkpointsHandler = IModelHost.iModelClient.checkpoints;
sinon.stub(checkpointsHandler, "get").callsFake(async () => [mockCheckpoint]);
sinon.stub(IModelHost.iModelClient, "checkpoints").get(() => checkpointsHandler);

const fileHandler = IModelHost.iModelClient.fileHandler!;
sinon.stub(fileHandler, "downloadFile").callsFake(async (_requestContext: AuthorizedClientRequestContext, _downloadUrl: string, downloadPath: string) => {
IModelJsFs.copySync(dbPath, downloadPath);
});
sinon.stub(IModelHost.iModelClient, "fileHandler").get(() => fileHandler);

const ctx = ClientRequestContext.current as AuthorizedClientRequestContext;
const downloadedDbPath = IModelTestUtils.prepareOutputFile("IModel", "TestCheckpoint2.bim");
await V1CheckpointManager.downloadCheckpoint({ localFile: downloadedDbPath, checkpoint: { requestContext: ctx, contextId, iModelId, changeSetId } });
const db = SnapshotDb.openCheckpointV1(downloadedDbPath, { requestContext: ctx, contextId, iModelId, changeSetId });
assert.equal(iModelId, db.nativeDb.getDbGuid(), "expected the V1 Checkpoint download to fix the improperly set dbGuid.");
});
});

describe("Checkpoint Manager", () => {

afterEach(() => {
sinon.restore();
});

it("open missing local file should return undefined", async () => {
const checkpoint = {
contextId: "5678",
iModelId: "910",
changeSetId: "1234",
requestContext: new AuthorizedClientRequestContext(new AccessToken()), // Why is this on CheckpointProps rather than DownloadRequest
};
const request = {
localFile: V1CheckpointManager.getFileName(checkpoint),
checkpoint,
};
const db = CheckpointManager.tryOpenLocalFile(request);
assert.isUndefined(db);
});

it("open a bad bim file should return undefined", async () => {
const checkpoint = {
contextId: "5678",
iModelId: "910",
changeSetId: "1234",
requestContext: new AuthorizedClientRequestContext(new AccessToken()), // Why is this on CheckpointProps rather than DownloadRequest
};

// Setup a local file
const folder = V1CheckpointManager.getFolder(checkpoint.iModelId);
if (!IModelJsFs.existsSync(folder))
IModelJsFs.recursiveMkDirSync(folder);

const outputFile = V1CheckpointManager.getFileName(checkpoint);
if (IModelJsFs.existsSync(outputFile))
IModelJsFs.unlinkSync(outputFile);

IModelJsFs.writeFileSync(outputFile, "Testing");

// Attempt to open the file
const request = {
localFile: V1CheckpointManager.getFileName(checkpoint),
checkpoint,
};
const db = CheckpointManager.tryOpenLocalFile(request);
assert.isUndefined(db);
});

});
18 changes: 5 additions & 13 deletions core/backend/src/test/standalone/IModel.test.ts
Expand Up @@ -64,14 +64,6 @@ function exerciseGc() {
}
}

function generateChangeSetId(): string {
let result = "";
for (let i = 0; i < 20; ++i) {
result += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
}
return result;
}

describe("iModel", () => {
let imodel1: SnapshotDb;
let imodel2: SnapshotDb;
Expand Down Expand Up @@ -1709,7 +1701,7 @@ describe("iModel", () => {
const snapshot = SnapshotDb.createEmpty(dbPath, { rootSubject: { name: "test" } });
const iModelId = snapshot.getGuid();
const contextId = Guid.createValue();
const changeSetId = generateChangeSetId();
const changeSetId = IModelTestUtils.generateChangeSetId();
snapshot.nativeDb.saveProjectGuid(Guid.normalize(contextId));
snapshot.nativeDb.saveLocalValue("ParentChangeSetId", changeSetId); // even fake checkpoints need a changeSetId!
snapshot.saveChanges();
Expand Down Expand Up @@ -1762,7 +1754,7 @@ describe("iModel", () => {
const snapshot = SnapshotDb.createEmpty(dbPath, { rootSubject: { name: "test" } });
const iModelId = Guid.createValue(); // This is wrong - it should be `snapshot.getGuid()`!
const contextId = Guid.createValue();
const changeSetId = generateChangeSetId();
const changeSetId = IModelTestUtils.generateChangeSetId();
snapshot.nativeDb.saveProjectGuid(Guid.normalize(contextId));
snapshot.nativeDb.saveLocalValue("ParentChangeSetId", changeSetId);
snapshot.saveChanges();
Expand Down Expand Up @@ -1797,7 +1789,7 @@ describe("iModel", () => {
it("should throw when opening checkpoint without blockcache dir env", async () => {
process.env.BLOCKCACHE_DIR = "";
const ctx = ClientRequestContext.current as AuthorizedClientRequestContext;
const error = await getIModelError(SnapshotDb.openCheckpointV2({ requestContext: ctx, contextId: Guid.createValue(), iModelId: Guid.createValue(), changeSetId: generateChangeSetId() }));
const error = await getIModelError(SnapshotDb.openCheckpointV2({ requestContext: ctx, contextId: Guid.createValue(), iModelId: Guid.createValue(), changeSetId: IModelTestUtils.generateChangeSetId() }));
expectIModelError(IModelStatus.BadRequest, error);
});

Expand All @@ -1808,11 +1800,11 @@ describe("iModel", () => {
sinon.stub(IModelHost.iModelClient, "checkpointsV2").get(() => checkpointsV2Handler);

const ctx = ClientRequestContext.current as AuthorizedClientRequestContext;
let error = await getIModelError(SnapshotDb.openCheckpointV2({ requestContext: ctx, contextId: Guid.createValue(), iModelId: Guid.createValue(), changeSetId: generateChangeSetId() }));
let error = await getIModelError(SnapshotDb.openCheckpointV2({ requestContext: ctx, contextId: Guid.createValue(), iModelId: Guid.createValue(), changeSetId: IModelTestUtils.generateChangeSetId() }));
expectIModelError(IModelStatus.NotFound, error);

hubMock.callsFake(async () => [{} as any]);
error = await getIModelError(SnapshotDb.openCheckpointV2({ requestContext: ctx, contextId: Guid.createValue(), iModelId: Guid.createValue(), changeSetId: generateChangeSetId() }));
error = await getIModelError(SnapshotDb.openCheckpointV2({ requestContext: ctx, contextId: Guid.createValue(), iModelId: Guid.createValue(), changeSetId: IModelTestUtils.generateChangeSetId() }));
expectIModelError(IModelStatus.BadRequest, error);
});

Expand Down

0 comments on commit ebf11e0

Please sign in to comment.