Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests to check for V1CheckpointManager fixing dbGuid if mis-match. #1064

Merged
merged 7 commits into from Mar 30, 2021
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@calebmshafer I thought I would make this api public for downloading V2 checkpoints. But, the public way to do that is BriefcaseManager.downloadBriefcase that takes a RequestNewBriefcaseArg, so everything about CheckpointManager should be 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