Merge pull request #5356 from mitc-gjuge/feat/infomaniak-record-filter

perf(dns): filter Infomaniak records server-side when upserting
This commit is contained in:
Narciso E. Núñez Arias 2026-09-08 13:13:43 -04:00 committed by GitHub
commit 467c80a73d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 78 additions and 6 deletions

View File

@ -326,6 +326,58 @@ describe("infomaniakClient.upsertRecord", () => {
expect(lastBody().target).toBe('"token-value"');
});
it("queries the API with a source and type filter instead of the whole zone", async () => {
mockFetch
.mockResolvedValueOnce(ikSuccess([]))
.mockResolvedValueOnce(ikSuccess({ id: 50 }));
await infomaniakClient.upsertRecord(config, {
zoneId: "example.com",
type: "A",
name: "app.example.com",
content: "1.2.3.4",
});
const [url] = mockFetch.mock.calls[0] as [string];
expect(url).toContain("filter%5Bsource%5D=app");
expect(url).toContain("filter%5Btypes%5D%5B%5D=A");
});
it("ignores a partial filter hit rather than overwriting a different record", async () => {
// filter[source] matches substrings: asking for "auto" also returns
// "autoconfig" and "autodiscover". Trusting it would overwrite one of them.
mockFetch
.mockResolvedValueOnce(
ikSuccess([
{
id: 61,
type: "CNAME",
source: "autoconfig",
target: "a.example.net",
ttl: 300,
},
{
id: 62,
type: "CNAME",
source: "autodiscover",
target: "b.example.net",
ttl: 300,
},
]),
)
.mockResolvedValueOnce(ikSuccess({ id: 63 }));
const result = await infomaniakClient.upsertRecord(config, {
zoneId: "example.com",
type: "CNAME",
name: "auto.example.com",
content: "c.example.net",
});
expect(result).toEqual({ id: "63" });
expect(lastCall()[1].method).toBe("POST");
});
});
describe("infomaniakClient.updateRecord", () => {

View File

@ -148,6 +148,31 @@ const listZoneRecords = async (config: InfomaniakConfig, zoneId: string) =>
`/2/zones/${encodeURIComponent(zoneId)}/records?with=records_description`,
);
// The API filters server-side, which avoids pulling a whole zone just to find
// one record. The match is still checked here: filter[source] is documented with
// a bare subdomain example, so nothing guarantees it compares exactly the way
// toSource writes the apex, and a filter that silently over-matches would
// otherwise turn an update into a duplicate.
const findRecord = async (
config: InfomaniakConfig,
zoneId: string,
type: string,
source: string,
) => {
const query = new URLSearchParams({
"filter[source]": source,
"filter[types][]": type,
});
const candidates = await ikFetch<InfomaniakRecord[]>(
config,
`/2/zones/${encodeURIComponent(zoneId)}/records?${query}`,
);
return candidates.find(
(candidate) =>
candidate.type === type && normalizeSource(candidate.source) === source,
);
};
export const infomaniakClient: DnsClient<InfomaniakConfig> = {
async listZones(config) {
const domains = await listDomainProducts(config);
@ -171,12 +196,7 @@ export const infomaniakClient: DnsClient<InfomaniakConfig> = {
async upsertRecord(config, record) {
const source = toSource(record.name, record.zoneId);
const existing = await listZoneRecords(config, record.zoneId);
const match = existing.find(
(candidate) =>
candidate.type === record.type &&
normalizeSource(candidate.source) === source,
);
const match = await findRecord(config, record.zoneId, record.type, source);
const body = JSON.stringify(recordPayload(record, record.zoneId));
const zone = encodeURIComponent(record.zoneId);