-
Notifications
You must be signed in to change notification settings - Fork 1k
Tests for app-* commands #8909
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
base: master
Are you sure you want to change the base?
Tests for app-* commands #8909
Changes from 1 commit
7c06d1a
18eb9e8
c15e1b9
046f691
c1f0c44
712c755
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { expect } from "chai"; | ||
import * as sinon from "sinon"; | ||
import { Command } from "../command"; | ||
import * as projectUtils from "../projectUtils"; | ||
import * as apps from "../management/apps"; | ||
import { ShaCertificateType } from "../management/apps"; | ||
import * as utils from "../utils"; | ||
import { command, getCertHashType } from "./apps-android-sha-create"; | ||
|
||
describe("apps:android:sha:create", () => { | ||
let sandbox: sinon.SinonSandbox; | ||
let needProjectIdStub: sinon.SinonStub; | ||
let createAppAndroidShaStub: sinon.SinonStub; | ||
let promiseWithSpinnerStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
sandbox = sinon.createSandbox(); | ||
needProjectIdStub = sandbox.stub(projectUtils, "needProjectId").returns("test-project-id"); | ||
createAppAndroidShaStub = sandbox | ||
.stub(apps, "createAppAndroidSha") | ||
.resolves({ name: "test-sha", shaHash: "test-hash", certType: ShaCertificateType.SHA_1 }); | ||
promiseWithSpinnerStub = sandbox.stub(utils, "promiseWithSpinner").callThrough(); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
it("should be a Command", () => { | ||
expect(command).to.be.an.instanceOf(Command); | ||
}); | ||
|
||
describe("action", () => { | ||
it("should create a SHA certificate", async () => { | ||
const shaHash = "A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2"; // SHA-1 | ||
await command.runner()("test-app-id", shaHash, {}); | ||
|
||
expect(needProjectIdStub).to.have.been.calledOnce; | ||
expect(createAppAndroidShaStub).to.have.been.calledOnce; | ||
const spinnerText = promiseWithSpinnerStub.getCall(0).args[1]; | ||
expect(spinnerText).to.include("Creating Android SHA certificate"); | ||
}); | ||
}); | ||
|
||
describe("getCertHashType", () => { | ||
it("should return SHA_1 for a 40-character hash", () => { | ||
const shaHash = "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"; | ||
expect(getCertHashType(shaHash)).to.equal(ShaCertificateType.SHA_1); | ||
}); | ||
|
||
it("should return SHA_256 for a 64-character hash", () => { | ||
const shaHash = "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"; | ||
expect(getCertHashType(shaHash)).to.equal(ShaCertificateType.SHA_256); | ||
}); | ||
|
||
it("should return UNSPECIFIED for other hash lengths", () => { | ||
const shaHash = "A1B2C3D4E5F6"; | ||
expect(getCertHashType(shaHash)).to.equal( | ||
ShaCertificateType.SHA_CERTIFICATE_TYPE_UNSPECIFIED, | ||
); | ||
}); | ||
|
||
it("should handle colons in the hash", () => { | ||
const shaHash = "A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2:C3:D4:E5:F6:A1:B2"; | ||
expect(getCertHashType(shaHash)).to.equal(ShaCertificateType.SHA_1); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||||
import { expect } from "chai"; | ||||||
import * as sinon from "sinon"; | ||||||
import { Command } from "../command"; | ||||||
import * as projectUtils from "../projectUtils"; | ||||||
import * as apps from "../management/apps"; | ||||||
import * as utils from "../utils"; | ||||||
import { command } from "./apps-android-sha-delete"; | ||||||
|
||||||
describe("apps:android:sha:delete", () => { | ||||||
let sandbox: sinon.SinonSandbox; | ||||||
let needProjectIdStub: sinon.SinonStub; | ||||||
let deleteAppAndroidShaStub: sinon.SinonStub; | ||||||
let promiseWithSpinnerStub: sinon.SinonStub; | ||||||
|
||||||
beforeEach(() => { | ||||||
sandbox = sinon.createSandbox(); | ||||||
needProjectIdStub = sandbox.stub(projectUtils, "needProjectId").returns("test-project-id"); | ||||||
deleteAppAndroidShaStub = sandbox.stub(apps, "deleteAppAndroidSha").resolves(); | ||||||
promiseWithSpinnerStub = sandbox.stub(utils, "promiseWithSpinner").callThrough(); | ||||||
}); | ||||||
|
||||||
afterEach(() => { | ||||||
sandbox.restore(); | ||||||
}); | ||||||
|
||||||
it("should be a Command", () => { | ||||||
expect(command).to.be.an.instanceOf(Command); | ||||||
}); | ||||||
|
||||||
describe("action", () => { | ||||||
it("should delete a SHA certificate", async () => { | ||||||
await command.runner()("test-app-id", "test-sha-id", {}); | ||||||
|
||||||
expect(needProjectIdStub).to.have.been.calledOnce; | ||||||
expect(deleteAppAndroidShaStub).to.have.been.calledOnce; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make this test more specific, consider asserting that
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ can we address this as well? |
||||||
const spinnerText = promiseWithSpinnerStub.getCall(0).args[1]; | ||||||
expect(spinnerText).to.include("Deleting Android SHA certificate hash"); | ||||||
}); | ||||||
}); | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { expect } from "chai"; | ||
import * as sinon from "sinon"; | ||
import * as Table from "cli-table3"; | ||
import { Command } from "../command"; | ||
import * as projectUtils from "../projectUtils"; | ||
import * as apps from "../management/apps"; | ||
import { AppAndroidShaData, ShaCertificateType } from "../management/apps"; | ||
import * as utils from "../utils"; | ||
import { command, logCertificatesList, logCertificatesCount } from "./apps-android-sha-list"; | ||
|
||
describe("apps:android:sha:list", () => { | ||
let sandbox: sinon.SinonSandbox; | ||
let promiseWithSpinnerStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
sandbox = sinon.createSandbox(); | ||
sandbox.stub(projectUtils, "needProjectId").returns("test-project-id"); | ||
sandbox.stub(apps, "listAppAndroidSha"); | ||
promiseWithSpinnerStub = sandbox.stub(utils, "promiseWithSpinner"); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
it("should be a Command", () => { | ||
expect(command).to.be.an.instanceOf(Command); | ||
}); | ||
|
||
describe("action", () => { | ||
it("should list SHA certificates", async () => { | ||
const certificates: AppAndroidShaData[] = [ | ||
{ | ||
name: "projects/p/androidApps/a/sha/s1", | ||
shaHash: "h1", | ||
certType: ShaCertificateType.SHA_1, | ||
}, | ||
{ | ||
name: "projects/p/androidApps/a/sha/s2", | ||
shaHash: "h2", | ||
certType: ShaCertificateType.SHA_256, | ||
}, | ||
]; | ||
promiseWithSpinnerStub.resolves(certificates); | ||
|
||
await command.runner()("test-app-id", {}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test stubs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this! |
||
expect(promiseWithSpinnerStub).to.have.been.calledOnce; | ||
const spinnerText = promiseWithSpinnerStub.getCall(0).args[1]; | ||
expect(spinnerText).to.include("Preparing the list"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we also check that the logger logged the list that you stubbed above? |
||
}); | ||
|
||
it('should display "No SHA certificate hashes found." if no certificates exist', async () => { | ||
promiseWithSpinnerStub.resolves([]); | ||
|
||
await command.runner()("test-app-id", {}); | ||
|
||
// No assertion needed here, we are just checking that it does not throw. | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test only checks that the command doesn't throw an error when no certificates are found. A stronger assertion would be to verify that the correct message is logged to the user. You can achieve this by spying on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we address this? |
||
}); | ||
|
||
describe("logCertificatesList", () => { | ||
it("should print a table of certificates", () => { | ||
const certificates: AppAndroidShaData[] = [ | ||
{ | ||
name: "projects/p/androidApps/app1/sha/sha1", | ||
shaHash: "hash1", | ||
certType: ShaCertificateType.SHA_1, | ||
}, | ||
{ | ||
name: "projects/p/androidApps/app2/sha/sha2", | ||
shaHash: "hash2", | ||
certType: ShaCertificateType.SHA_256, | ||
}, | ||
]; | ||
const tableSpy = sandbox.spy(Table.prototype, "push"); | ||
|
||
logCertificatesList(certificates); | ||
|
||
expect(tableSpy.getCall(0).args[0]).to.deep.equal([ | ||
"app1", | ||
"sha1", | ||
"hash1", | ||
ShaCertificateType.SHA_1, | ||
]); | ||
expect(tableSpy.getCall(1).args[0]).to.deep.equal([ | ||
"app2", | ||
"sha2", | ||
"hash2", | ||
ShaCertificateType.SHA_256, | ||
]); | ||
}); | ||
}); | ||
|
||
describe("logCertificatesCount", () => { | ||
it("should print the total number of certificates", () => { | ||
logCertificatesCount(5); | ||
// No assertion needed here, we are just checking that it does not throw. | ||
}); | ||
|
||
it("should not print if count is 0", () => { | ||
logCertificatesCount(0); | ||
// No assertion needed here, we are just checking that it does not throw. | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { expect } from "chai"; | ||
import * as sinon from "sinon"; | ||
import { Command } from "../command"; | ||
import * as projectUtils from "../projectUtils"; | ||
import { FirebaseError } from "../error"; | ||
import * as apps from "../management/apps"; | ||
import { AppPlatform } from "../management/apps"; | ||
import * as prompt from "../prompt"; | ||
import { command, logPostAppCreationInformation } from "./apps-create"; | ||
|
||
describe("apps:create", () => { | ||
let sandbox: sinon.SinonSandbox; | ||
let needProjectIdStub: sinon.SinonStub; | ||
let getAppPlatformStub: sinon.SinonStub; | ||
let sdkInitStub: sinon.SinonStub; | ||
let selectStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
sandbox = sinon.createSandbox(); | ||
needProjectIdStub = sandbox.stub(projectUtils, "needProjectId").returns("test-project-id"); | ||
getAppPlatformStub = sandbox.stub(apps, "getAppPlatform"); | ||
sdkInitStub = sandbox.stub(apps, "sdkInit").resolves({ | ||
name: "test-name", | ||
projectId: "test-project-id", | ||
appId: "test-app-id", | ||
platform: AppPlatform.WEB, | ||
displayName: "test-display-name", | ||
}); | ||
selectStub = sandbox.stub(prompt, "select"); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
it("should be a Command", () => { | ||
expect(command).to.be.an.instanceOf(Command); | ||
}); | ||
|
||
describe("action", () => { | ||
it("should throw if platform is not provided in non-interactive mode", async () => { | ||
getAppPlatformStub.returns(AppPlatform.ANY); | ||
const options = { nonInteractive: true }; | ||
await expect(command.runner()("", undefined, options)).to.be.rejectedWith( | ||
FirebaseError, | ||
"App platform must be provided", | ||
); | ||
expect(needProjectIdStub).to.have.been.calledOnce; | ||
}); | ||
|
||
it("should prompt for platform if not provided in interactive mode", async () => { | ||
getAppPlatformStub.withArgs("").returns(AppPlatform.ANY); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be here? |
||
getAppPlatformStub.withArgs("IOS").returns(AppPlatform.IOS); | ||
selectStub.resolves("IOS"); | ||
const options = { nonInteractive: false }; | ||
await command.runner()("", "test-display-name", options); | ||
expect(selectStub).to.have.been.calledOnce; | ||
expect(sdkInitStub).to.have.been.calledOnceWith( | ||
AppPlatform.IOS, | ||
sinon.match({ | ||
displayName: "test-display-name", | ||
nonInteractive: false, | ||
}), | ||
); | ||
}); | ||
|
||
it("should create an iOS app", async () => { | ||
getAppPlatformStub.returns(AppPlatform.IOS); | ||
const options = { bundleId: "test-bundle-id" }; | ||
await command.runner()("IOS", "test-display-name", options); | ||
expect(sdkInitStub).to.have.been.calledOnceWith( | ||
AppPlatform.IOS, | ||
sinon.match({ | ||
bundleId: "test-bundle-id", | ||
displayName: "test-display-name", | ||
}), | ||
); | ||
}); | ||
|
||
it("should create an Android app", async () => { | ||
getAppPlatformStub.returns(AppPlatform.ANDROID); | ||
const options = { packageName: "test-package-name" }; | ||
await command.runner()("ANDROID", "test-display-name", options); | ||
expect(sdkInitStub).to.have.been.calledOnceWith( | ||
AppPlatform.ANDROID, | ||
sinon.match({ | ||
packageName: "test-package-name", | ||
displayName: "test-display-name", | ||
}), | ||
); | ||
}); | ||
|
||
it("should create a Web app", async () => { | ||
getAppPlatformStub.returns(AppPlatform.WEB); | ||
const options = {}; | ||
await command.runner()("WEB", "test-display-name", options); | ||
expect(sdkInitStub).to.have.been.calledOnceWith( | ||
AppPlatform.WEB, | ||
sinon.match({ | ||
displayName: "test-display-name", | ||
}), | ||
); | ||
}); | ||
}); | ||
|
||
describe("logPostAppCreationInformation", () => { | ||
it("should log basic app information", () => { | ||
const appMetadata: apps.WebAppMetadata = { | ||
name: "test-name", | ||
projectId: "test-project-id", | ||
appId: "test-app-id", | ||
platform: AppPlatform.WEB, | ||
displayName: "test-display-name", | ||
}; | ||
logPostAppCreationInformation(appMetadata, AppPlatform.WEB); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// No assertion needed here, we are just checking that it does not throw. | ||
}); | ||
|
||
it("should log iOS specific information", () => { | ||
const appMetadata: apps.IosAppMetadata = { | ||
name: "test-name", | ||
projectId: "test-project-id", | ||
appId: "test-app-id", | ||
platform: AppPlatform.IOS, | ||
displayName: "test-display-name", | ||
bundleId: "test-bundle-id", | ||
appStoreId: "test-app-store-id", | ||
}; | ||
logPostAppCreationInformation(appMetadata, AppPlatform.IOS); | ||
// No assertion needed here, we are just checking that it does not throw. | ||
}); | ||
|
||
it("should log Android specific information", () => { | ||
const appMetadata: apps.AndroidAppMetadata = { | ||
name: "test-name", | ||
projectId: "test-project-id", | ||
appId: "test-app-id", | ||
platform: AppPlatform.ANDROID, | ||
displayName: "test-display-name", | ||
packageName: "test-package-name", | ||
}; | ||
logPostAppCreationInformation(appMetadata, AppPlatform.ANDROID); | ||
// No assertion needed here, we are just checking that it does not throw. | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test checks that
createAppAndroidShaStub
is called, but it could be more robust by also verifying that it's called with the correct arguments. This ensures the command is correctly interpreting its inputs and passing them to the management API.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we address this ^?