/**
 * Copyright 2020 Inrupt Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

import { describe, it, expect } from "@jest/globals";
import { NamedNode } from "rdf-js";
import { DataFactory } from "n3";

import {
  asIri,
  createThing,
  getThing,
  getThingAll,
  isThing,
  setThing,
} from "../thing/thing";
import {
  addAgent,
  addNoneOfRuleUrl,
  addGroup,
  addAnyOfRuleUrl,
  addAllOfRuleUrl,
  createRule,
  getAgentAll,
  getNoneOfRuleUrlAll,
  getGroupAll,
  getAnyOfRuleUrlAll,
  getAllOfRuleUrlAll,
  removeNoneOfRuleUrl,
  removeAnyOfRuleUrl,
  removeAllOfRuleUrl,
  getRule,
  hasAuthenticated,
  hasPublic,
  removeAgent,
  removeGroup,
  Rule,
  setAgent,
  setAuthenticated,
  setNoneOfRuleUrl,
  setGroup,
  setAnyOfRuleUrl,
  setPublic,
  setAllOfRuleUrl,
  getRuleAll,
  setRule,
  hasCreator,
  setCreator,
  ruleAsMarkdown,
  removeRule,
  getClientAll,
  setClient,
  addClient,
  removeClient,
  hasAnyClient,
  setAnyClient,
  removePublic,
  removeAuthenticated,
  removeCreator,
  removeAnyClient,
  getResourceRule,
  getResourceRuleAll,
  removeResourceRule,
  setResourceRule,
  createResourceRuleFor,
} from "./rule";

import { Policy } from "./policy";
import { createSolidDataset } from "../resource/solidDataset";
import { setUrl } from "../thing/set";
import { Thing, ThingPersisted, Url, UrlString } from "../interfaces";
import { acp, rdf } from "../constants";
import {
  getIri,
  getIriAll,
  getSourceUrl,
  mockSolidDatasetFrom,
} from "../index";
import { addMockAcrTo, mockAcrFor } from "./mock";
import { internal_getAcr } from "./control.internal";

// Vocabulary terms
const ACP_ANY = DataFactory.namedNode("http://www.w3.org/ns/solid/acp#anyOf");
const ACP_ALL = DataFactory.namedNode("http://www.w3.org/ns/solid/acp#allOf");
const ACP_NONE = DataFactory.namedNode("http://www.w3.org/ns/solid/acp#noneOf");
const RDF_TYPE = DataFactory.namedNode(
  "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
);
const ACP_RULE = DataFactory.namedNode("http://www.w3.org/ns/solid/acp#Rule");
const ACP_AGENT = DataFactory.namedNode("http://www.w3.org/ns/solid/acp#agent");
const ACP_GROUP = DataFactory.namedNode("http://www.w3.org/ns/solid/acp#group");
const ACP_CLIENT = DataFactory.namedNode(
  "http://www.w3.org/ns/solid/acp#client"
);
const ACP_PUBLIC = DataFactory.namedNode(
  "http://www.w3.org/ns/solid/acp#PublicAgent"
);
const ACP_AUTHENTICATED = DataFactory.namedNode(
  "http://www.w3.org/ns/solid/acp#AuthenticatedAgent"
);
const ACP_CREATOR = DataFactory.namedNode(
  "http://www.w3.org/ns/solid/acp#CreatorAgent"
);
const SOLID_PUBLIC_CLIENT = DataFactory.namedNode(
  "http://www.w3.org/ns/solid/terms#PublicOidcClient"
);

// Test data
const MOCKED_POLICY_IRI = DataFactory.namedNode(
  "https://some.pod/policy-resource#policy"
);
const MOCKED_RULE_IRI = DataFactory.namedNode(
  "https://some.pod/rule-resource#a-rule"
);
const OTHER_MOCKED_RULE_IRI = DataFactory.namedNode(
  "https://some.pod/rule-resource#another-rule"
);
const ALLOF_RULE_IRI = DataFactory.namedNode(
  "https://some.pod/rule-resource#allOf-rule"
);
const ANYOF_RULE_IRI = DataFactory.namedNode(
  "https://some.pod/rule-resource#anyOf-rule"
);
const NONEOF_RULE_IRI = DataFactory.namedNode(
  "https://some.pod/rule-resource#noneOf-rule"
);
const MOCK_WEBID_ME = DataFactory.namedNode("https://my.pod/profile#me");
const MOCK_WEBID_YOU = DataFactory.namedNode("https://your.pod/profile#you");
const MOCK_GROUP_IRI = DataFactory.namedNode("https://my.pod/group#a-group");
const MOCK_GROUP_OTHER_IRI = DataFactory.namedNode(
  "https://my.pod/group#another-group"
);
const MOCK_CLIENT_WEBID_1 = DataFactory.namedNode(
  "https://my.app/registration#it"
);
const MOCK_CLIENT_WEBID_2 = DataFactory.namedNode(
  "https://your.app/registration#it"
);

type ThingObject = ThingPersisted | Url | UrlString;

function isNamedNode(object: ThingObject): object is Url {
  return typeof (object as Url).value !== undefined;
}

const addAllObjects = (
  thing: ThingPersisted,
  predicate: NamedNode,
  objects: ThingObject[]
): void => {
  objects.forEach((objectToAdd) => {
    let objectUrl: string;
    if (isThing(objectToAdd)) {
      objectUrl = asIri(objectToAdd);
    } else if (isNamedNode(objectToAdd)) {
      // The object is an Url (aka NamedNode)
      objectUrl = objectToAdd.value;
    } else {
      objectUrl = objectToAdd;
    }
    thing.add(
      DataFactory.quad(
        DataFactory.namedNode(asIri(thing)),
        predicate,
        DataFactory.namedNode(objectUrl)
      )
    );
  });
};

const mockRule = (
  url: Url,
  content?: {
    agents?: Url[];
    groups?: Url[];
    public?: boolean;
    authenticated?: boolean;
    creator?: boolean;
    clients?: Url[];
    publicClient?: boolean;
  }
): Rule => {
  let mockedRule = createThing({
    url: url.value,
  });
  mockedRule = mockedRule.add(
    DataFactory.quad(
      DataFactory.namedNode(asIri(mockedRule)),
      RDF_TYPE,
      ACP_RULE
    )
  );
  if (content?.agents) {
    addAllObjects(mockedRule, ACP_AGENT, content.agents);
  }
  if (content?.groups) {
    addAllObjects(mockedRule, ACP_GROUP, content.groups);
  }
  if (content?.clients) {
    addAllObjects(mockedRule, ACP_CLIENT, content.clients);
  }
  if (content?.public) {
    mockedRule = mockedRule.add(
      DataFactory.quad(
        DataFactory.namedNode(asIri(mockedRule)),
        ACP_AGENT,
        ACP_PUBLIC
      )
    );
  }
  if (content?.authenticated) {
    mockedRule = mockedRule.add(
      DataFactory.quad(
        DataFactory.namedNode(asIri(mockedRule)),
        ACP_AGENT,
        ACP_AUTHENTICATED
      )
    );
  }
  if (content?.creator) {
    mockedRule = mockedRule.add(
      DataFactory.quad(
        DataFactory.namedNode(asIri(mockedRule)),
        ACP_AGENT,
        ACP_CREATOR
      )
    );
  }
  if (content?.publicClient) {
    mockedRule = mockedRule.add(
      DataFactory.quad(
        DataFactory.namedNode(asIri(mockedRule)),
        ACP_CLIENT,
        SOLID_PUBLIC_CLIENT
      )
    );
  }
  return mockedRule;
};

const mockPolicy = (
  url: NamedNode,
  rules?: { allOf?: Rule[]; anyOf?: Rule[]; noneOf?: Rule[] }
): Policy => {
  const mockPolicy = createThing({ url: url.value });
  if (rules?.noneOf) {
    addAllObjects(mockPolicy, ACP_NONE, rules.noneOf);
  }
  if (rules?.anyOf) {
    addAllObjects(mockPolicy, ACP_ANY, rules.anyOf);
  }
  if (rules?.allOf) {
    addAllObjects(mockPolicy, ACP_ALL, rules.allOf);
  }
  return mockPolicy;
};

describe("addNoneOfRuleUrl", () => {
  it("adds the rule in the noneOf rules of the policy", () => {
    const myPolicy = addNoneOfRuleUrl(
      mockPolicy(MOCKED_POLICY_IRI),
      mockRule(MOCKED_RULE_IRI)
    );
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not remove the existing noneOf rules", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const myPolicy = addNoneOfRuleUrl(mockedPolicy, mockRule(MOCKED_RULE_IRI));
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, OTHER_MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the existing allOf and anyOf rules", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockRule(ANYOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const myPolicy = addNoneOfRuleUrl(mockedPolicy, mockRule(NONEOF_RULE_IRI));
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, ALLOF_RULE_IRI))
    ).toBe(true);
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, ANYOF_RULE_IRI))
    ).toBe(true);
  });

  it("does not change the input policy", () => {
    const myPolicy = mockPolicy(MOCKED_POLICY_IRI);
    const mypolicySize = myPolicy.size;
    addNoneOfRuleUrl(myPolicy, mockRule(MOCKED_RULE_IRI));
    expect(myPolicy.size).toBe(mypolicySize);
  });
});

describe("addAnyOfRuleUrl", () => {
  it("adds the rule in the anyOf rules of the policy", () => {
    const myPolicy = addAnyOfRuleUrl(
      mockPolicy(MOCKED_POLICY_IRI),
      mockRule(MOCKED_RULE_IRI)
    );
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not remove the existing anyOf rules", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const myPolicy = addAnyOfRuleUrl(mockedPolicy, mockRule(MOCKED_POLICY_IRI));
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, OTHER_MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the existing allOf and noneOf rules", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const myPolicy = addAnyOfRuleUrl(mockedPolicy, mockRule(ANYOF_RULE_IRI));
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, ALLOF_RULE_IRI))
    ).toBe(true);
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, NONEOF_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the input policy", () => {
    const myPolicy = mockPolicy(MOCKED_POLICY_IRI);
    addAnyOfRuleUrl(myPolicy, mockRule(MOCKED_RULE_IRI));
    expect(myPolicy.size).toBe(0);
  });
});

describe("addAllOfRule", () => {
  it("adds the rule in the allOf rules of the policy", () => {
    const myPolicy = addAllOfRuleUrl(
      mockPolicy(MOCKED_POLICY_IRI),
      mockRule(MOCKED_RULE_IRI)
    );
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not remove the existing allOf rules", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      allOf: [mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const myPolicy = addAllOfRuleUrl(mockedPolicy, mockRule(MOCKED_RULE_IRI));
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, OTHER_MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the existing anyOf and noneOf rules", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      anyOf: [mockRule(ANYOF_RULE_IRI)],
    });
    const myPolicy = addAllOfRuleUrl(mockedPolicy, mockRule(ANYOF_RULE_IRI));
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, ANYOF_RULE_IRI))
    ).toBe(true);
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, NONEOF_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the input policy", () => {
    const myPolicy = mockPolicy(MOCKED_POLICY_IRI);
    addAnyOfRuleUrl(myPolicy, mockRule(MOCKED_RULE_IRI));
    expect(myPolicy.size).toBe(0);
  });
});

describe("setNoneOfRuleUrl", () => {
  it("sets the provided rules as the noneOf rules for the policy", () => {
    const myPolicy = setNoneOfRuleUrl(
      mockPolicy(MOCKED_POLICY_IRI),
      mockRule(MOCKED_RULE_IRI)
    );
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("removes any previous noneOf rules for on the policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const myPolicy = setNoneOfRuleUrl(mockedPolicy, mockRule(MOCKED_RULE_IRI));
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, OTHER_MOCKED_RULE_IRI)
      )
    ).toBe(false);
  });

  it("does not change the existing anyOf and allOf rules on the policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockRule(ANYOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const myPolicy = setNoneOfRuleUrl(mockedPolicy, mockRule(NONEOF_RULE_IRI));
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, ALLOF_RULE_IRI))
    ).toBe(true);
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, ANYOF_RULE_IRI))
    ).toBe(true);
  });

  it("does not change the input policy", () => {
    const myPolicy = mockPolicy(MOCKED_POLICY_IRI);
    setNoneOfRuleUrl(myPolicy, mockRule(MOCKED_RULE_IRI));
    expect(myPolicy.size).toBe(0);
  });
});

describe("setAnyOfRuleUrl", () => {
  it("sets the provided rules as the anyOf rules for the policy", () => {
    const myPolicy = setAnyOfRuleUrl(
      mockPolicy(MOCKED_POLICY_IRI),
      mockRule(MOCKED_RULE_IRI)
    );
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("removes any previous anyOf rules for on the policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const myPolicy = setAnyOfRuleUrl(mockedPolicy, mockRule(MOCKED_RULE_IRI));
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, OTHER_MOCKED_RULE_IRI)
      )
    ).toBe(false);
  });

  it("does not change the existing noneOf and allOf rules on the policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const myPolicy = setAnyOfRuleUrl(mockedPolicy, mockRule(ANYOF_RULE_IRI));
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, ALLOF_RULE_IRI))
    ).toBe(true);
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, NONEOF_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the input policy", () => {
    const myPolicy = mockPolicy(MOCKED_POLICY_IRI);
    setAnyOfRuleUrl(myPolicy, mockRule(MOCKED_RULE_IRI));
    expect(myPolicy.size).toBe(0);
  });
});

describe("setAllOfRuleUrl", () => {
  it("sets the provided rules as the allOf rules for the policy", () => {
    const myPolicy = setAllOfRuleUrl(
      mockPolicy(MOCKED_POLICY_IRI),
      mockRule(MOCKED_RULE_IRI)
    );
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, MOCKED_RULE_IRI)
      )
    ).toBe(true);
  });

  it("removes any previous allOf rules for on the policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      allOf: [mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const myPolicy = setAllOfRuleUrl(mockedPolicy, mockRule(MOCKED_RULE_IRI));
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, OTHER_MOCKED_RULE_IRI)
      )
    ).toBe(false);
  });

  it("does not change the existing noneOf and anyOf rules on the policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      anyOf: [mockRule(ANYOF_RULE_IRI)],
    });
    const myPolicy = setAllOfRuleUrl(mockedPolicy, mockRule(ALLOF_RULE_IRI));
    expect(
      myPolicy.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, ANYOF_RULE_IRI))
    ).toBe(true);
    expect(
      myPolicy.has(
        DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, NONEOF_RULE_IRI)
      )
    ).toBe(true);
  });

  it("does not change the input policy", () => {
    const myPolicy = mockPolicy(MOCKED_POLICY_IRI);
    setAllOfRuleUrl(myPolicy, mockRule(MOCKED_RULE_IRI));
    expect(myPolicy.size).toBe(0);
  });
});

describe("getNoneOfRuleurlAll", () => {
  it("returns all the noneOf rules for the given policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(MOCKED_RULE_IRI), mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const noneOfRules = getNoneOfRuleUrlAll(mockedPolicy);
    expect(noneOfRules).toContain(MOCKED_RULE_IRI.value);
    expect(noneOfRules).toContain(OTHER_MOCKED_RULE_IRI.value);
    expect(noneOfRules).toHaveLength(2);
  });

  it("returns only the noneOf rules for the given policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      anyOf: [mockRule(ANYOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const noneOfRules = getNoneOfRuleUrlAll(mockedPolicy);
    expect(noneOfRules).not.toContain(ANYOF_RULE_IRI.value);
    expect(noneOfRules).not.toContain(ALLOF_RULE_IRI.value);
    expect(noneOfRules).toHaveLength(1);
  });
});

describe("getAnyOfRulesOnPolicyAll", () => {
  it("returns all the anyOf rules for the given policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockRule(MOCKED_RULE_IRI), mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const anyOfRules = getAnyOfRuleUrlAll(mockedPolicy);
    expect(anyOfRules).toContain(MOCKED_RULE_IRI.value);
    expect(anyOfRules).toContain(OTHER_MOCKED_RULE_IRI.value);
    expect(anyOfRules).toHaveLength(2);
  });

  it("returns only the anyOf rules for the given policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      anyOf: [mockRule(ANYOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const anyOfRules = getAnyOfRuleUrlAll(mockedPolicy);
    expect(anyOfRules).not.toContain(NONEOF_RULE_IRI.value);
    expect(anyOfRules).not.toContain(ALLOF_RULE_IRI.value);
    expect(anyOfRules).toHaveLength(1);
  });
});

describe("getAllOfRulesOnPolicyAll", () => {
  it("returns all the allOf rules for the given policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      allOf: [mockRule(MOCKED_RULE_IRI), mockRule(OTHER_MOCKED_RULE_IRI)],
    });
    const allOfRules = getAllOfRuleUrlAll(mockedPolicy);
    expect(allOfRules).toContain(MOCKED_RULE_IRI.value);
    expect(allOfRules).toContain(OTHER_MOCKED_RULE_IRI.value);
    expect(allOfRules).toHaveLength(2);
  });

  it("returns only the allOf rules for the given policy", () => {
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockRule(NONEOF_RULE_IRI)],
      anyOf: [mockRule(ANYOF_RULE_IRI)],
      allOf: [mockRule(ALLOF_RULE_IRI)],
    });
    const allOfRules = getAllOfRuleUrlAll(mockedPolicy);
    expect(allOfRules).not.toContain(NONEOF_RULE_IRI.value);
    expect(allOfRules).not.toContain(ANYOF_RULE_IRI.value);
    expect(allOfRules).toHaveLength(1);
  });
});

describe("removeAllOfRule", () => {
  it("removes the rule from the allOf rules for the given policy", () => {
    const mockedRule = mockRule(MOCKED_RULE_IRI);
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      allOf: [mockedRule],
    });
    const result = removeAllOfRuleUrl(mockedPolicy, mockedRule);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, MOCKED_RULE_IRI))
    ).toBe(false);
  });

  it("does not remove the rule from the anyOf/noneOf rules for the given policy", () => {
    const mockedRule = mockRule(MOCKED_RULE_IRI);
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockedRule],
      noneOf: [mockedRule],
    });
    const result = removeAllOfRuleUrl(mockedPolicy, mockedRule);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, MOCKED_RULE_IRI))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, MOCKED_RULE_IRI))
    ).toBe(true);
  });
});

describe("removeAnyOfRuleUrl", () => {
  it("removes the rule from the allOf rules for the given policy", () => {
    const mockedRule = mockRule(MOCKED_RULE_IRI);
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      anyOf: [mockedRule],
    });
    const result = removeAnyOfRuleUrl(mockedPolicy, mockedRule);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, MOCKED_RULE_IRI))
    ).toBe(false);
  });

  it("does not remove the rule from the allOf/noneOf rules for the given policy", () => {
    const mockedRule = mockRule(MOCKED_RULE_IRI);
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      allOf: [mockedRule],
      noneOf: [mockedRule],
    });
    const result = removeAnyOfRuleUrl(mockedPolicy, mockedRule);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, MOCKED_RULE_IRI))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, MOCKED_RULE_IRI))
    ).toBe(true);
  });
});

describe("removeNoneOfRuleUrl", () => {
  it("removes the rule from the noneOf rules for the given policy", () => {
    const mockedRule = mockRule(MOCKED_RULE_IRI);
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      noneOf: [mockedRule],
    });
    const result = removeNoneOfRuleUrl(mockedPolicy, mockedRule);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_NONE, MOCKED_RULE_IRI))
    ).toBe(false);
  });

  it("does not remove the rule from the allOf/anyOf rules for the given policy", () => {
    const mockedRule = mockRule(MOCKED_RULE_IRI);
    const mockedPolicy = mockPolicy(MOCKED_POLICY_IRI, {
      allOf: [mockedRule],
      anyOf: [mockedRule],
    });
    const result = removeNoneOfRuleUrl(mockedPolicy, mockedRule);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ALL, MOCKED_RULE_IRI))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_POLICY_IRI, ACP_ANY, MOCKED_RULE_IRI))
    ).toBe(true);
  });
});

describe("createRule", () => {
  it("returns a acp:Rule", () => {
    const myRule = createRule(MOCKED_RULE_IRI.value);
    expect(
      myRule.has(DataFactory.quad(MOCKED_RULE_IRI, RDF_TYPE, ACP_RULE))
    ).toBe(true);
  });
  it("returns an **empty** rule", () => {
    const myRule = createRule("https://my.pod/rule-resource#rule");
    // The rule should only contain a type triple.
    expect(myRule.size).toBe(1);
  });
});

describe("createResourceRuleFor", () => {
  it("returns a acp:Rule", () => {
    const mockedAcr = mockAcrFor("https://some.pod/resource");
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    const myRule = createResourceRuleFor(mockedResourceWithAcr, "myRule");
    expect(getIri(myRule, RDF_TYPE)).toBe(ACP_RULE.value);
  });
  it("returns an **empty** rule", () => {
    const mockedAcr = mockAcrFor("https://some.pod/resource");
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    const myRule = createResourceRuleFor(mockedResourceWithAcr, "myRule");
    // The rule should only contain a type triple.
    expect(myRule.size).toBe(1);
  });
});

describe("getRule", () => {
  it("returns the rule with a matching IRI", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const dataset = setThing(createSolidDataset(), rule);
    const result = getRule(dataset, MOCKED_RULE_IRI.value);
    expect(result).not.toBeNull();
  });

  it("does not return a Thing with a matching IRI but the wrong type", () => {
    const notARule = createThing({
      url: "https://my.pod/rule-resource#not-a-rule",
    });
    const dataset = setThing(
      createSolidDataset(),
      setUrl(notARule, RDF_TYPE, "http://example.org/ns#NotRuleType")
    );
    const result = getRule(dataset, "https://my.pod/rule-resource#not-a-rule");
    expect(result).toBeNull();
  });

  it("does not return a rule with a mismatching IRI", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const dataset = setThing(createSolidDataset(), rule);
    const result = getRule(dataset, OTHER_MOCKED_RULE_IRI);
    expect(result).toBeNull();
  });
});

describe("getResourceRule", () => {
  it("returns the rule with a matching name", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    const result = getResourceRule(mockedResourceWithAcr, "rule");
    expect(result).not.toBeNull();
  });

  it("does not return a Thing with a matching IRI but the wrong type", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(
      mockedRule,
      rdf.type,
      "http://example.org/ns#NotRuleType"
    );
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    const result = getResourceRule(mockedResourceWithAcr, "rule");
    expect(result).toBeNull();
  });

  it("does not return a rule with a mismatching IRI", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    const result = getResourceRule(mockedResourceWithAcr, "other-rule");
    expect(result).toBeNull();
  });
});

describe("getRuleAll", () => {
  it("returns an empty array if there are no Rules in the given Dataset", () => {
    expect(getRuleAll(createSolidDataset())).toHaveLength(0);
  });

  it("returns all the rules in a rule resource", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const dataset = setThing(createSolidDataset(), rule);
    let result = getRuleAll(dataset);
    expect(result).toHaveLength(1);

    const anotherRule = mockRule(OTHER_MOCKED_RULE_IRI);
    const newDataset = setThing(dataset, anotherRule);
    result = getRuleAll(newDataset);
    expect(result).toHaveLength(2);
  });
});

describe("getResourceRuleAll", () => {
  it("returns an empty array if there are no Rules in the given Resource's ACR", () => {
    const mockedAcr = mockAcrFor("https://some.pod/resource");
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    expect(getResourceRuleAll(mockedResourceWithAcr)).toHaveLength(0);
  });

  it("returns all the rules in a Resource's ACR", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule1 = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule1`,
    });
    mockedRule1 = setUrl(mockedRule1, rdf.type, acp.Rule);
    let mockedRule2 = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule2`,
    });
    mockedRule2 = setUrl(mockedRule2, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule1);
    mockedAcr = setThing(mockedAcr, mockedRule2);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );

    const result = getResourceRuleAll(mockedResourceWithAcr);
    expect(result).toHaveLength(2);
  });
});

describe("removeRule", () => {
  it("removes the Rule from the given empty Dataset", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const dataset = setThing(createSolidDataset(), rule);

    const updatedDataset = removeRule(dataset, MOCKED_RULE_IRI);
    expect(getThingAll(updatedDataset)).toHaveLength(0);
  });
});

describe("removeResourceRule", () => {
  it("removes the Rule from the given Resource's Access control Resource", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );

    const updatedDataset = removeResourceRule(
      mockedResourceWithAcr,
      mockedRule
    );
    expect(getResourceRuleAll(updatedDataset)).toHaveLength(0);
  });

  it("accepts a plain name to remove a Rule", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );

    const updatedDataset = removeResourceRule(mockedResourceWithAcr, "rule");
    expect(getResourceRuleAll(updatedDataset)).toHaveLength(0);
  });

  it("accepts a full URL to remove a Rule", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );

    const updatedDataset = removeResourceRule(
      mockedResourceWithAcr,
      `${getSourceUrl(mockedAcr)}#rule`
    );
    expect(getResourceRuleAll(updatedDataset)).toHaveLength(0);
  });

  it("accepts a Named Node to remove a Rule", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );

    const updatedDataset = removeResourceRule(
      mockedResourceWithAcr,
      DataFactory.namedNode(`${getSourceUrl(mockedAcr)}#rule`)
    );
    expect(getResourceRuleAll(updatedDataset)).toHaveLength(0);
  });

  it("does not remove a non-Rule with the same name", () => {
    let mockedAcr = mockAcrFor("https://some.pod/resource");
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(
      mockedRule,
      rdf.type,
      "https://example.vocab/not-a-rule"
    );
    mockedAcr = setThing(mockedAcr, mockedRule);
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );

    const updatedDataset = removeResourceRule(mockedResourceWithAcr, "rule");
    const updatedAcr = internal_getAcr(updatedDataset);
    expect(
      getThing(updatedAcr, `${getSourceUrl(mockedAcr)}#rule`)
    ).not.toBeNull();
  });
});

describe("setRule", () => {
  it("sets the Rule in the given empty Dataset", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const dataset = setRule(createSolidDataset(), rule);

    const result = getThing(dataset, MOCKED_RULE_IRI);
    expect(result).not.toBeNull();
    expect(getIriAll(result as Thing, rdf.type)).toContain(acp.Rule);
  });
});

describe("setResourceRule", () => {
  it("sets the Rule in the given Resource's ACR", () => {
    const mockedAcr = mockAcrFor("https://some.pod/resource");
    const mockedResourceWithAcr = addMockAcrTo(
      mockSolidDatasetFrom("https://some.pod/resource"),
      mockedAcr
    );
    let mockedRule = createThing({
      url: `${getSourceUrl(mockedAcr)}#rule`,
    });
    mockedRule = setUrl(mockedRule, rdf.type, acp.Rule);
    const updatedResource = setResourceRule(mockedResourceWithAcr, mockedRule);

    expect(getResourceRuleAll(updatedResource)).toHaveLength(1);
  });
});

describe("getAgentAll", () => {
  it("returns all the agents a rule applies to by WebID", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME, MOCK_WEBID_YOU],
    });
    const agents = getAgentAll(rule);
    expect(agents).toContain(MOCK_WEBID_ME.value);
    expect(agents).toContain(MOCK_WEBID_YOU.value);
    expect(agents).toHaveLength(2);
  });

  it("does not return the groups/public/authenticated/creator/clients a rule applies to", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_IRI],
      public: true,
      authenticated: true,
      creator: true,
      clients: [MOCK_CLIENT_WEBID_1],
    });
    const agents = getAgentAll(rule);
    expect(agents).not.toContain(MOCK_GROUP_IRI.value);
    expect(agents).not.toContain(ACP_CREATOR.value);
    expect(agents).not.toContain(ACP_AUTHENTICATED.value);
    expect(agents).not.toContain(ACP_PUBLIC.value);
    expect(agents).toHaveLength(0);
  });
});

describe("setAgent", () => {
  it("sets the given agents for the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setAgent(rule, MOCK_WEBID_ME.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });

  it("deletes any agents previously set for the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_YOU],
    });
    const result = setAgent(rule, MOCK_WEBID_ME.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_YOU))
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_YOU],
    });
    setAgent(rule, MOCK_WEBID_ME.value);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(false);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_YOU))
    ).toBe(true);
  });

  it("does not overwrite public, authenticated and creator agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
      authenticated: true,
      creator: true,
    });
    const result = setAgent(rule, MOCK_WEBID_YOU.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);

    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);

    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_CREATOR))
    ).toBe(true);
  });
});

describe("addAgent", () => {
  it("adds the given agent to the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = addAgent(rule, MOCK_WEBID_YOU.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_YOU))
    ).toBe(true);
  });

  it("does not override existing agents/public/authenticated/groups", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME],
      groups: [MOCK_GROUP_IRI],
      public: true,
      authenticated: true,
    });
    const result = addAgent(rule, MOCK_WEBID_YOU.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_YOU))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(true);
  });
});

describe("removeAgent", () => {
  it("removes the given agent from the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_YOU],
    });
    const result = removeAgent(rule, MOCK_WEBID_YOU.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_YOU))
    ).toBe(false);
  });

  it("does not delete unrelated agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME, MOCK_WEBID_YOU],
      public: true,
      authenticated: true,
    });
    const result = removeAgent(rule, MOCK_WEBID_YOU.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_YOU))
    ).toBe(false);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);
  });

  it("does not remove groups, even with matching IRI", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_IRI],
    });
    const result = removeAgent(rule, MOCK_GROUP_IRI.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(true);
  });
});

describe("getGroupAll", () => {
  it("returns all the groups a rule applies to", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_IRI, MOCK_GROUP_OTHER_IRI],
    });
    const groups = getGroupAll(rule);
    expect(groups).toContain(MOCK_GROUP_IRI.value);
    expect(groups).toContain(MOCK_GROUP_OTHER_IRI.value);
    expect(groups).toHaveLength(2);
  });

  it("does not return the agents/public/authenticated/clients a rule applies to", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME],
      public: true,
      authenticated: true,
      clients: [MOCK_CLIENT_WEBID_1],
    });
    const groups = getGroupAll(rule);
    expect(groups).not.toContain(MOCK_WEBID_ME.value);
    expect(groups).not.toContain(ACP_AUTHENTICATED.value);
    expect(groups).not.toContain(ACP_PUBLIC.value);
    expect(groups).toHaveLength(0);
  });
});

describe("setGroup", () => {
  it("sets the given groups for the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setGroup(rule, MOCK_GROUP_IRI.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(true);
  });

  it("deletes any groups previously set for the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_OTHER_IRI],
    });
    const result = setGroup(rule, MOCK_GROUP_IRI.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_OTHER_IRI)
      )
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_OTHER_IRI],
    });
    setGroup(rule, MOCK_GROUP_IRI.value);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(false);
    expect(
      rule.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_OTHER_IRI)
      )
    ).toBe(true);
  });
});

describe("addGroup", () => {
  it("adds the given group to the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = addGroup(rule, "https://your.pod/groups#a-group");
    expect(
      result.has(
        DataFactory.quad(
          MOCKED_RULE_IRI,
          ACP_GROUP,
          DataFactory.namedNode("https://your.pod/groups#a-group")
        )
      )
    ).toBe(true);
  });

  it("does not override existing agents/public/authenticated/groups", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME],
      groups: [MOCK_GROUP_IRI],
      public: true,
      authenticated: true,
    });
    const result = addGroup(rule, MOCK_GROUP_OTHER_IRI.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_OTHER_IRI)
      )
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(true);
  });
});

describe("removeGroup", () => {
  it("removes the given group from the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_IRI],
    });
    const result = removeGroup(rule, MOCK_GROUP_IRI.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(false);
  });

  it("does not delete unrelated groups", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_IRI, MOCK_GROUP_OTHER_IRI],
    });
    const result = removeGroup(rule, MOCK_GROUP_IRI.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(false);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_OTHER_IRI)
      )
    ).toBe(true);
  });

  it("does not remove agents, even with matching IRI", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME],
    });
    const result = removeGroup(rule, MOCK_WEBID_ME.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });
});

describe("hasPublic", () => {
  it("returns true if the rule applies to the public agent", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
    });
    expect(hasPublic(rule)).toBe(true);
  });
  it("returns false if the rule only applies to other agent", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: false,
      authenticated: true,
      agents: [MOCK_WEBID_ME],
    });
    expect(hasPublic(rule)).toBe(false);
  });
});

describe("setPublic", () => {
  it("applies the given rule to the public agent", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setPublic(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    setPublic(rule);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(false);
  });

  it("does not change the other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      authenticated: true,
      agents: [MOCK_WEBID_ME],
    });
    const result = setPublic(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });

  it("throws an error when you attempt to use the deprecated API", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    expect(
      // @ts-expect-error The type signature should warn about passing a second argument:
      () => setPublic(rule, true)
    ).toThrow(
      "The function `setPublic` no longer takes a second parameter. It is now used together with `removePublic` instead."
    );
  });
});

describe("removePublic", () => {
  it("prevents the rule from applying to the public agent", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
    });
    const result = removePublic(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, { public: true });
    removePublic(rule);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
  });

  it("does not change the other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      authenticated: true,
      agents: [MOCK_WEBID_ME],
      public: true,
    });
    const result = removePublic(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });
});

describe("hasAuthenticated", () => {
  it("returns true if the rule applies to authenticated agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      authenticated: true,
    });
    expect(hasAuthenticated(rule)).toBe(true);
  });
  it("returns false if the rule only applies to other agent", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
      authenticated: false,
      agents: [MOCK_WEBID_ME],
    });
    expect(hasAuthenticated(rule)).toBe(false);
  });
});

describe("setAuthenticated", () => {
  it("applies to given rule to authenticated agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setAuthenticated(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(true);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    setAuthenticated(rule);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED))
    ).toBe(false);
  });

  it("does not change the other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
      agents: [MOCK_WEBID_ME],
    });
    const result = setAuthenticated(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });

  it("throws an error when you attempt to use the deprecated API", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    expect(
      // @ts-expect-error The type signature should warn about passing a second argument:
      () => setAuthenticated(rule, true)
    ).toThrow(
      "The function `setAuthenticated` no longer takes a second parameter. It is now used together with `removeAuthenticated` instead."
    );
  });
});

describe("removeAuthenticated", () => {
  it("prevents the rule from applying to authenticated agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      authenticated: true,
    });
    const result = removeAuthenticated(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED)
      )
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, { authenticated: true });
    removeAuthenticated(rule);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_AUTHENTICATED))
    ).toBe(true);
  });

  it("does not change the other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
      authenticated: true,
      agents: [MOCK_WEBID_ME],
    });
    const result = removeAuthenticated(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });
});

describe("hasCreator", () => {
  it("returns true if the rule applies to the Resource's creator", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      creator: true,
    });
    expect(hasCreator(rule)).toBe(true);
  });
  it("returns false if the rule only applies to other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
      creator: false,
      agents: [MOCK_WEBID_ME],
    });
    expect(hasCreator(rule)).toBe(false);
  });
});

describe("setCreator", () => {
  it("applies the given rule to the Resource's creator", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setCreator(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_CREATOR))
    ).toBe(true);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    setCreator(rule);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_CREATOR))
    ).toBe(false);
  });

  it("does not change the other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      public: true,
      agents: [MOCK_WEBID_ME],
    });
    const result = setCreator(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });

  it("throws an error when you attempt to use the deprecated API", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    expect(
      // @ts-expect-error The type signature should warn about passing a second argument:
      () => setCreator(rule, true)
    ).toThrow(
      "The function `setCreator` no longer takes a second parameter. It is now used together with `removeCreator` instead."
    );
  });
});

describe("removeCreator", () => {
  it("prevents the rule from applying to the Resource's creator", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      creator: true,
    });
    const result = removeCreator(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_CREATOR))
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, { creator: true });
    removeCreator(rule);
    expect(
      rule.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_CREATOR))
    ).toBe(true);
  });

  it("does not change the other agents", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      creator: true,
      public: true,
      agents: [MOCK_WEBID_ME],
    });
    const result = removeCreator(rule);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, ACP_PUBLIC))
    ).toBe(true);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });
});

describe("getClientAll", () => {
  it("returns all the clients a rule applies to by WebID", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1, MOCK_CLIENT_WEBID_2],
    });
    const clients = getClientAll(rule);
    expect(clients).toContain(MOCK_CLIENT_WEBID_1.value);
    expect(clients).toContain(MOCK_CLIENT_WEBID_2.value);
    expect(clients).toHaveLength(2);
  });

  it("does not return the agents/groups/public client a rule applies to", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME],
      groups: [MOCK_GROUP_IRI],
      public: true,
      authenticated: true,
      creator: true,
      publicClient: true,
    });
    const clients = getClientAll(rule);
    expect(clients).not.toContain(MOCK_GROUP_IRI.value);
    expect(clients).not.toContain(ACP_CREATOR.value);
    expect(clients).not.toContain(ACP_AUTHENTICATED.value);
    expect(clients).not.toContain(ACP_PUBLIC.value);
    expect(clients).toHaveLength(0);
  });
});

describe("setClient", () => {
  it("sets the given clients for the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setClient(rule, MOCK_CLIENT_WEBID_1.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
  });

  it("deletes any clients previously set for the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1],
    });
    const result = setClient(rule, MOCK_CLIENT_WEBID_2.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_2)
      )
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1],
    });
    setClient(rule, MOCK_CLIENT_WEBID_2.value);
    expect(
      rule.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_2)
      )
    ).toBe(false);
    expect(
      rule.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
  });

  it("does not overwrite the public client class", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      publicClient: true,
    });
    const result = setClient(rule, MOCK_CLIENT_WEBID_1.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(true);
  });
});

describe("addClient", () => {
  it("adds the given client to the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = addClient(rule, MOCK_CLIENT_WEBID_1.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
  });

  it("does not override existing clients/the public client class", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1],
      publicClient: true,
    });
    const result = addClient(rule, MOCK_CLIENT_WEBID_2.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_2)
      )
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(true);
  });
});

describe("removeClient", () => {
  it("removes the given client from the rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1],
    });
    const result = removeClient(rule, MOCK_CLIENT_WEBID_1.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(false);
  });

  it("does not delete unrelated clients", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1, MOCK_CLIENT_WEBID_2],
      publicClient: true,
    });
    const result = removeClient(rule, MOCK_CLIENT_WEBID_2.value);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_2)
      )
    ).toBe(false);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(true);
  });

  it("does not remove agents, even with a matching IRI", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      agents: [MOCK_WEBID_ME],
    });
    const result = removeClient(rule, MOCK_WEBID_ME.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_AGENT, MOCK_WEBID_ME))
    ).toBe(true);
  });

  it("does not remove groups, even with a matching IRI", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      groups: [MOCK_GROUP_IRI],
    });
    const result = removeClient(rule, MOCK_GROUP_IRI.value);
    expect(
      result.has(DataFactory.quad(MOCKED_RULE_IRI, ACP_GROUP, MOCK_GROUP_IRI))
    ).toBe(true);
  });
});

describe("hasAnyClient", () => {
  it("returns true if the rule applies to any client", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      publicClient: true,
    });
    expect(hasAnyClient(rule)).toBe(true);
  });
  it("returns false if the rule only applies to individual clients", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1],
    });
    expect(hasAnyClient(rule)).toBe(false);
  });
});

describe("setAnyClient", () => {
  it("applies to given rule to the public client class", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    const result = setAnyClient(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(true);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI);
    setAnyClient(rule);
    expect(
      rule.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(false);
  });

  it("does not change the other clients", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      clients: [MOCK_CLIENT_WEBID_1],
    });
    const result = setAnyClient(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
  });
});

describe("removeAnyClient", () => {
  it("prevents the rule from applying to the public client class", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      publicClient: true,
    });
    const result = removeAnyClient(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(false);
  });

  it("does not change the input rule", () => {
    const rule = mockRule(MOCKED_RULE_IRI, { publicClient: true });
    removeAnyClient(rule);
    expect(
      rule.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, SOLID_PUBLIC_CLIENT)
      )
    ).toBe(true);
  });

  it("does not change the other clients", () => {
    const rule = mockRule(MOCKED_RULE_IRI, {
      publicClient: true,
      clients: [MOCK_CLIENT_WEBID_1],
    });
    const result = removeAnyClient(rule);
    expect(
      result.has(
        DataFactory.quad(MOCKED_RULE_IRI, ACP_CLIENT, MOCK_CLIENT_WEBID_1)
      )
    ).toBe(true);
  });
});

describe("ruleAsMarkdown", () => {
  it("shows when a rule is empty", () => {
    const rule = createRule("https://some.pod/policyResource#rule");

    expect(ruleAsMarkdown(rule)).toBe(
      "## Rule: https://some.pod/policyResource#rule\n" + "\n" + "<empty>\n"
    );
  });

  it("can show everything to which the rule applies", () => {
    let rule = createRule("https://some.pod/policyResource#rule");
    rule = setCreator(rule);
    rule = setAuthenticated(rule);
    rule = setPublic(rule);
    rule = setAnyClient(rule);
    rule = addAgent(rule, "https://some.pod/profile#agent");
    rule = addAgent(rule, "https://some-other.pod/profile#agent");
    rule = addGroup(rule, "https://some.pod/groups#family");
    rule = addClient(rule, "https://some.app/registration#it");

    expect(ruleAsMarkdown(rule)).toBe(
      "## Rule: https://some.pod/policyResource#rule\n" +
        "\n" +
        "This rule applies to:\n" +
        "- Everyone\n" +
        "- All authenticated agents\n" +
        "- The creator of this resource\n" +
        "- Users of any client application\n" +
        "- The following agents:\n" +
        "  - https://some.pod/profile#agent\n" +
        "  - https://some-other.pod/profile#agent\n" +
        "- Members of the following groups:\n" +
        "  - https://some.pod/groups#family\n" +
        "- Users of the following client applications:\n" +
        "  - https://some.app/registration#it\n"
    );
  });
});
