← back to Open Seo

src/client/features/backlinks/export.test.ts

178 lines

import { describe, expect, it } from "vitest";
import { buildCsv } from "@/client/lib/csv";
import type { BacklinksRow, ReferringDomainRow } from "./backlinksPageTypes";
import {
  buildBacklinksTabCsvFilename,
  buildBacklinksTabExport,
} from "./export";

function makeBacklinkRow(overrides: Partial<BacklinksRow> = {}): BacklinksRow {
  return {
    domainFrom: "example.org",
    urlFrom: "https://example.org/post",
    urlTo: "https://example.com/path",
    anchor: "Example",
    itemType: "organic",
    isDofollow: true,
    relAttributes: ["noopener", "noreferrer"],
    rank: 123,
    domainFromRank: 45,
    pageFromRank: 12,
    spamScore: 10,
    firstSeen: "2025-01-01",
    lastSeen: "2025-01-15",
    isLost: false,
    isBroken: false,
    linksCount: 2,
    ...overrides,
  };
}

function makeReferringDomainRow(
  overrides: Partial<ReferringDomainRow> = {},
): ReferringDomainRow {
  return {
    domain: "source.com",
    backlinks: 12,
    referringPages: 7,
    rank: 101,
    spamScore: 4,
    firstSeen: "2024-05-10",
    brokenBacklinks: 1,
    brokenPages: 0,
    ...overrides,
  };
}

function buildTabCsv(
  ...args: Parameters<typeof buildBacklinksTabExport>
): string {
  const { headers, rows } = buildBacklinksTabExport(...args);
  return buildCsv(headers, rows);
}

describe("buildBacklinksTabCsvFilename", () => {
  it("normalizes the target into the filename per tab", () => {
    expect(
      buildBacklinksTabCsvFilename("backlinks", "https://Example.com/path?q=1"),
    ).toBe("backlinks-backlinks-example.com-path-q-1.csv");
    expect(buildBacklinksTabCsvFilename("domains", "Example.com")).toBe(
      "backlinks-referring-domains-example.com.csv",
    );
    expect(buildBacklinksTabCsvFilename("pages", "docs.example.com")).toBe(
      "backlinks-top-pages-docs.example.com.csv",
    );
  });
});

describe("buildBacklinksTabExport", () => {
  it("builds backlinks csv with backlink-specific columns", () => {
    const content = buildTabCsv({
      tab: "backlinks",
      rows: {
        backlinks: [makeBacklinkRow()],
        referringDomains: [],
        topPages: [],
      },
    });

    expect(content).toContain('"Domain","Source URL","Target URL"');
    expect(content).not.toContain('"Ahrefs DR"');
    expect(content).toContain('"example.org"');
    expect(content).toContain('"noopener, noreferrer"');
  });

  it("includes an Ahrefs DR column when ratings are loaded", () => {
    const content = buildTabCsv({
      tab: "domains",
      domainRatings: { "source.com": 71.5, "other.com": null },
      rows: {
        backlinks: [],
        referringDomains: [makeReferringDomainRow()],
        topPages: [],
      },
    });

    expect(content).toContain('"Rank","Ahrefs DR","Spam Score"');
    expect(content).toContain('"71.5"');
  });

  it("keys backlink Ahrefs DR off the www-stripped source domain", () => {
    const content = buildTabCsv({
      tab: "backlinks",
      domainRatings: { "example.org": 33 },
      rows: {
        backlinks: [makeBacklinkRow({ domainFrom: "www.example.org" })],
        referringDomains: [],
        topPages: [],
      },
    });

    expect(content).toContain('"Domain Rank","Ahrefs DR","Source Page Rank"');
    expect(content).toContain('"33"');
  });

  it("builds referring domains csv", () => {
    const content = buildTabCsv({
      tab: "domains",
      rows: {
        backlinks: [],
        referringDomains: [makeReferringDomainRow()],
        topPages: [],
      },
    });

    expect(content).toContain('"Domain","Backlinks","Referring Pages"');
    expect(content).toContain('"source.com"');
  });

  it("builds top pages csv", () => {
    const content = buildTabCsv({
      tab: "pages",
      rows: {
        backlinks: [],
        referringDomains: [],
        topPages: [
          {
            page: "https://docs.example.com/start",
            backlinks: 22,
            referringDomains: 9,
            rank: 88,
            brokenBacklinks: 0,
          },
        ],
      },
    });

    expect(content).toContain(
      '"Page","Backlinks","Referring Domains","Rank","Broken Backlinks"',
    );
    expect(content).toContain('"https://docs.example.com/start"');
  });

  it("sanitizes formula-like cell values to prevent CSV injection", () => {
    const content = buildTabCsv({
      tab: "backlinks",
      rows: {
        backlinks: [
          makeBacklinkRow({
            domainFrom: "=cmd|' /C calc'!A0",
            urlFrom: "+https://evil.example/source",
            urlTo: "@https://evil.example/target",
            anchor: "\tformula",
            relAttributes: [],
            linksCount: 1,
          }),
        ],
        referringDomains: [],
        topPages: [],
      },
    });

    expect(content).toContain("\"'=cmd|' /C calc'!A0\"");
    expect(content).toContain('"\'+https://evil.example/source"');
    expect(content).toContain('"\'@https://evil.example/target"');
    expect(content).toContain('"\'\tformula"');
  });
});