Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

- Added `Registrar.restoreDomain` and `Registrar.getDomainRestore` to restore a
domain and retrieve a domain restore, and the `DomainRestore` type. (#309)
- Added `Zones.batchChangeZoneRecords` to create, update, and delete zone
records in a single request, and the `BatchChangeZoneRecordsResponse` type. (#313)

### Changed

Expand Down
6 changes: 6 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ export type ZoneRecord = {
updated_at: string;
};

export type BatchChangeZoneRecordsResponse = {
creates: Array<ZoneRecord>;
updates: Array<ZoneRecord>;
deletes: Array<{ id: number }>;
};

export type Contact = {
id: number;
account_id: number;
Expand Down
45 changes: 45 additions & 0 deletions lib/zones.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,51 @@ export class Zones {
return method;
})();

/**
* Performs multiple record operations (create, update and/or delete) for a zone in a single request for atomicity and efficiency.
*
* POST /{account}/zones/{zone}/batch
*
* @see https://developer.dnsimple.com/v2/zones/records/#batchChangeZoneRecords
*
* @param account The account id
* @param zone The zone name
* @param params Query parameters
*/
batchChangeZoneRecords = (() => {
const method = (
account: number,
zone: string,
data: Partial<{
creates: Array<{
name: string;
type: string;
content: string;
ttl?: number;
priority?: number;
regions?: Array<types.ZoneRecordRegion>;
}>;
updates: Array<{
id: number;
name?: string;
content?: string;
ttl?: number;
priority?: number;
regions?: Array<types.ZoneRecordRegion>;
}>;
deletes: Array<{ id: number }>;
}>,
params: QueryParams & {} = {}
): Promise<{ data: types.BatchChangeZoneRecordsResponse }> =>
this._client.request(
"POST",
`/${account}/zones/${zone}/batch`,
data,
params
);
return method;
})();

/**
* Checks if a zone record is fully distributed to all our name servers across the globe.
*
Expand Down
84 changes: 83 additions & 1 deletion test/zone_records.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fetchMock from "fetch-mock";
import { NotFoundError } from "../lib/main";
import { ClientError, NotFoundError } from "../lib/main";
import { createTestClient, responseFromFixture } from "./util";

const dnsimple = createTestClient();
Expand Down Expand Up @@ -311,4 +311,86 @@ describe("zone records", () => {
});
});
});

describe("#batchChangeZoneRecords", () => {
const accountId = 1010;
const zoneId = "example.com";
const attributes = {
creates: [
{ name: "ab", type: "A", content: "3.2.3.4" },
{ name: "ab", type: "A", content: "4.2.3.4" },
],
updates: [
{ id: 67622534, content: "3.2.3.40" },
{ id: 67622537, content: "5.2.3.40" },
],
deletes: [{ id: 67622509 }, { id: 67622527 }],
};

it("builds the correct request", async () => {
fetchMock.post(
"https://api.dnsimple.com/v2/1010/zones/example.com/batch",
responseFromFixture("batchChangeZoneRecords/success.http")
);

await dnsimple.zones.batchChangeZoneRecords(
accountId,
zoneId,
attributes
);

expect(fetchMock.callHistory.lastCall().options.body).toEqual(
JSON.stringify(attributes)
);
});

it("produces the changed records", async () => {
fetchMock.post(
"https://api.dnsimple.com/v2/1010/zones/example.com/batch",
responseFromFixture("batchChangeZoneRecords/success.http")
);

const response = await dnsimple.zones.batchChangeZoneRecords(
accountId,
zoneId,
attributes
);

const { creates, updates, deletes } = response.data;
expect(creates.length).toBe(2);
expect(creates[0].id).toBe(67623409);
expect(creates[1].id).toBe(67623410);
expect(updates.length).toBe(2);
expect(updates[0].id).toBe(67622534);
expect(updates[1].id).toBe(67622537);
expect(deletes).toEqual([{ id: 67622509 }, { id: 67622527 }]);
});

describe("when the validation fails", () => {
it("produces an error", async () => {
fetchMock.post(
"https://api.dnsimple.com/v2/1010/zones/example.com/batch",
responseFromFixture(
"batchChangeZoneRecords/error_400_create_validation_failed.http"
)
);

const error = await dnsimple.zones
.batchChangeZoneRecords(accountId, zoneId, {
creates: [{ name: "ab", type: "XYZ", content: "3.2.3.4" }],
})
.catch((e) => e);

expect(error).toBeInstanceOf(ClientError);
const clientError = error as ClientError;
expect(clientError.status).toBe(400);
expect(clientError.data.message).toBe("Validation failed");
expect(clientError.attributeErrors().creates[0]).toEqual({
index: 0,
message: "Validation failed",
errors: { record_type: ["unsupported"] },
});
});
});
});
});
Loading