diff --git a/src/security-rules/security-rules-api-client.ts b/src/security-rules/security-rules-api-client.ts index ca3fd376fc..d96ceb4622 100644 --- a/src/security-rules/security-rules-api-client.ts +++ b/src/security-rules/security-rules-api-client.ts @@ -103,6 +103,20 @@ export class SecurityRulesApiClient { return this.sendRequest(request); } + public deleteRuleset(name: string): Promise { + return Promise.resolve() + .then(() => { + return this.getRulesetName(name); + }) + .then((rulesetName) => { + const request: HttpRequestConfig = { + method: 'DELETE', + url: `${this.url}/${rulesetName}`, + }; + return this.sendRequest(request); + }); + } + public getRelease(name: string): Promise { return this.getResource(`releases/${name}`); } diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index 2b54f40a7d..dd2725a20a 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -164,6 +164,18 @@ export class SecurityRules implements FirebaseServiceInterface { }); } + /** + * Deletes the Ruleset identified by the given name. The input name should be the short name string without + * the project ID prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`, pass the + * short name "my-ruleset". Rejects with a `not-found` error if the specified Ruleset cannot be found. + * + * @param {string} name Name of the Ruleset to delete. + * @returns {Promise} A promise that fulfills when the Ruleset is deleted. + */ + public deleteRuleset(name: string): Promise { + return this.client.deleteRuleset(name); + } + private getRulesetForRelease(releaseName: string): Promise { return this.client.getRelease(releaseName) .then((release) => { diff --git a/test/unit/security-rules/security-rules-api-client.spec.ts b/test/unit/security-rules/security-rules-api-client.spec.ts index 87063a6f9d..433cf7050b 100644 --- a/test/unit/security-rules/security-rules-api-client.spec.ts +++ b/test/unit/security-rules/security-rules-api-client.spec.ts @@ -304,4 +304,76 @@ describe('SecurityRulesApiClient', () => { .should.eventually.be.rejected.and.deep.equal(expected); }); }); + + describe('deleteRuleset', () => { + const INVALID_NAMES: any[] = [null, undefined, '', 1, true, {}, []]; + INVALID_NAMES.forEach((invalidName) => { + it(`should reject when called with: ${JSON.stringify(invalidName)}`, () => { + return apiClient.deleteRuleset(invalidName) + .should.eventually.be.rejected.and.have.property( + 'message', 'Ruleset name must be a non-empty string.'); + }); + }); + + it(`should reject when called with prefixed name`, () => { + return apiClient.deleteRuleset('projects/foo/rulesets/bar') + .should.eventually.be.rejected.and.have.property( + 'message', 'Ruleset name must not contain any "/" characters.'); + }); + + it('should resolve on success', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .resolves(utils.responseFrom({})); + stubs.push(stub); + return apiClient.deleteRuleset(RULESET_NAME) + .then(() => { + expect(stub).to.have.been.calledOnce.and.calledWith({ + method: 'DELETE', + url: 'https://firebaserules.googleapis.com/v1/projects/test-project/rulesets/ruleset-id', + }); + }); + }); + + it('should throw when a full platform error response is received', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(utils.errorFrom(ERROR_RESPONSE, 404)); + stubs.push(stub); + const expected = new FirebaseSecurityRulesError('not-found', 'Requested entity not found'); + return apiClient.deleteRuleset(RULESET_NAME) + .should.eventually.be.rejected.and.deep.equal(expected); + }); + + it('should throw unknown-error when error code is not present', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(utils.errorFrom({}, 404)); + stubs.push(stub); + const expected = new FirebaseSecurityRulesError('unknown-error', 'Unknown server error: {}'); + return apiClient.deleteRuleset(RULESET_NAME) + .should.eventually.be.rejected.and.deep.equal(expected); + }); + + it('should throw unknown-error for non-json response', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(utils.errorFrom('not json', 404)); + stubs.push(stub); + const expected = new FirebaseSecurityRulesError( + 'unknown-error', 'Unexpected response with status: 404 and body: not json'); + return apiClient.deleteRuleset(RULESET_NAME) + .should.eventually.be.rejected.and.deep.equal(expected); + }); + + it('should throw when rejected with a FirebaseAppError', () => { + const expected = new FirebaseAppError('network-error', 'socket hang up'); + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(expected); + stubs.push(stub); + return apiClient.deleteRuleset(RULESET_NAME) + .should.eventually.be.rejected.and.deep.equal(expected); + }); + }); }); diff --git a/test/unit/security-rules/security-rules.spec.ts b/test/unit/security-rules/security-rules.spec.ts index 3ead22b361..16c78c3ba3 100644 --- a/test/unit/security-rules/security-rules.spec.ts +++ b/test/unit/security-rules/security-rules.spec.ts @@ -342,4 +342,24 @@ describe('SecurityRules', () => { }); }); }); + + describe('deleteRuleset', () => { + it('should propagate API errors', () => { + const stub = sinon + .stub(SecurityRulesApiClient.prototype, 'deleteRuleset') + .rejects(EXPECTED_ERROR); + stubs.push(stub); + return securityRules.deleteRuleset('foo') + .should.eventually.be.rejected.and.deep.equal(EXPECTED_ERROR); + }); + + it('should resolve on success', () => { + const stub = sinon + .stub(SecurityRulesApiClient.prototype, 'deleteRuleset') + .resolves({}); + stubs.push(stub); + + return securityRules.deleteRuleset('foo'); + }); + }); });