use github_graphql_node_count::{node_count, point_aggregate, point_cost, Variables};
fn no_variables() -> Variables {
Variables::new()
}
fn page(value: u32) -> Variables {
Variables::from([("page".to_string(), value)])
}
const NESTED_PATH: &str = r#"
query {
viewer {
repositories(first: 10) {
edges {
node {
issues(first: 5) {
edges { node { title } }
}
}
}
}
}
}
"#;
const NESTED_PATH_NODES: u64 = 60;
#[test]
fn a_nested_connection_multiplies_by_its_parents_page_size() {
assert_eq!(
node_count(NESTED_PATH, &no_variables()),
Ok(NESTED_PATH_NODES)
);
}
const SIBLING_PATHS: &str = r#"
query {
viewer {
repositories(first: 10) { edges { node { name } } }
followers(first: 7) { edges { node { login } } }
following(first: 3) { edges { node { login } } }
}
}
"#;
const SIBLING_PATHS_NODES: u64 = 20;
#[test]
fn sibling_connections_sum_rather_than_compete() {
assert_eq!(
node_count(SIBLING_PATHS, &no_variables()),
Ok(SIBLING_PATHS_NODES)
);
assert_ne!(node_count(SIBLING_PATHS, &no_variables()), Ok(10));
}
const SPREAD_FRAGMENT: &str = r#"
query {
viewer {
repositories(first: 10) {
edges { node { ...RepositoryIssues } }
}
}
}
fragment RepositoryIssues on Repository {
name
issues(first: 4) {
edges { node { title } }
}
}
"#;
const SPREAD_FRAGMENT_NODES: u64 = 50;
#[test]
fn a_spread_counts_the_fragment_it_names() {
assert_eq!(
node_count(SPREAD_FRAGMENT, &no_variables()),
Ok(SPREAD_FRAGMENT_NODES)
);
}
const INLINE_FRAGMENT_UNION: &str = r#"
query {
repository(owner: "nickderobertis", name: "onetaskgraph") {
issue(number: 235) {
timelineItems(first: 10) {
nodes {
... on CrossReferencedEvent {
source {
... on PullRequest {
comments(first: 5) { nodes { bodyHTML } }
}
}
}
... on LabeledEvent {
label { name }
}
}
}
}
}
}
"#;
const INLINE_FRAGMENT_UNION_NODES: u64 = 60;
#[test]
fn an_inline_fragment_on_a_union_counts_against_its_parent() {
assert_eq!(
node_count(INLINE_FRAGMENT_UNION, &no_variables()),
Ok(INLINE_FRAGMENT_UNION_NODES)
);
}
const NESTED_SPREADS: &str = r#"
query {
viewer { ...ViewerRepositories }
}
fragment ViewerRepositories on User {
repositories(first: 6) {
edges { node { ...RepositoryIssues } }
}
}
fragment RepositoryIssues on Repository {
issues(first: 2) {
edges { node { title } }
}
}
"#;
const NESTED_SPREADS_NODES: u64 = 18;
#[test]
fn a_spread_reached_through_another_spread_is_counted() {
assert_eq!(
node_count(NESTED_SPREADS, &no_variables()),
Ok(NESTED_SPREADS_NODES)
);
}
const VARIABLE_SPENT_TWICE: &str = r#"
query ViewerIssues($page: Int!) {
viewer {
repositories(first: $page) {
edges {
node {
issues(first: $page) {
edges { node { title } }
}
}
}
}
}
}
"#;
const VARIABLE_SPENT_TWICE_AT_3: u64 = 12;
const VARIABLE_SPENT_TWICE_AT_5: u64 = 30;
const VARIABLE_SPENT_TWICE_AT_9: u64 = 90;
#[test]
fn one_variable_spent_twice_moves_the_total_by_its_square() {
assert_eq!(
node_count(VARIABLE_SPENT_TWICE, &page(3)),
Ok(VARIABLE_SPENT_TWICE_AT_3)
);
assert_eq!(
node_count(VARIABLE_SPENT_TWICE, &page(5)),
Ok(VARIABLE_SPENT_TWICE_AT_5)
);
assert_eq!(
node_count(VARIABLE_SPENT_TWICE, &page(9)),
Ok(VARIABLE_SPENT_TWICE_AT_9)
);
for (n, total) in [
(3u64, VARIABLE_SPENT_TWICE_AT_3),
(5, VARIABLE_SPENT_TWICE_AT_5),
(9, VARIABLE_SPENT_TWICE_AT_9),
] {
assert_eq!(total, n + n * n);
}
}
const OUTER_FIRST_LITERAL: &str = r#"
query {
viewer {
repositories(first: 25) {
edges { node { issues(first: 4) { edges { node { title } } } } }
}
}
}
"#;
const OUTER_LAST_LITERAL: &str = r#"
query {
viewer {
repositories(last: 25) {
edges { node { issues(first: 4) { edges { node { title } } } } }
}
}
}
"#;
const OUTER_FIRST_VARIABLE: &str = r#"
query ViewerIssues($page: Int!) {
viewer {
repositories(first: $page) {
edges { node { issues(first: 4) { edges { node { title } } } } }
}
}
}
"#;
const OUTER_LAST_VARIABLE: &str = r#"
query ViewerIssues($page: Int!) {
viewer {
repositories(last: $page) {
edges { node { issues(first: 4) { edges { node { title } } } } }
}
}
}
"#;
const PAGE_ARGUMENT_NODES: u64 = 125;
#[test]
fn first_and_last_and_literal_and_variable_all_count_the_same() {
assert_eq!(
node_count(OUTER_FIRST_LITERAL, &no_variables()),
Ok(PAGE_ARGUMENT_NODES)
);
assert_eq!(
node_count(OUTER_LAST_LITERAL, &no_variables()),
Ok(PAGE_ARGUMENT_NODES)
);
assert_eq!(
node_count(OUTER_FIRST_VARIABLE, &page(25)),
Ok(PAGE_ARGUMENT_NODES)
);
assert_eq!(
node_count(OUTER_LAST_VARIABLE, &page(25)),
Ok(PAGE_ARGUMENT_NODES)
);
}
const BOTH_FIRST_AND_LAST: &str = r#"
query {
viewer {
repositories(first: 3, last: 30) {
edges { node { name } }
}
}
}
"#;
const BOTH_FIRST_AND_LAST_NODES: u64 = 30;
const BOTH_FIRST_AND_LAST_NESTED: &str = r#"
query {
viewer {
repositories(first: 3, last: 30) {
edges { node { issues(first: 5) { edges { node { title } } } } }
}
}
}
"#;
const BOTH_FIRST_AND_LAST_NESTED_NODES: u64 = 180;
const BOTH_FIRST_AND_LAST_NESTED_AGGREGATE: u64 = 31;
#[test]
fn a_field_supplying_both_first_and_last_takes_the_larger() {
assert_eq!(
node_count(BOTH_FIRST_AND_LAST, &no_variables()),
Ok(BOTH_FIRST_AND_LAST_NODES)
);
}
const NO_PAGE_SIZE_ANYWHERE: &str = r#"
query {
viewer {
login
repository(owner: "nickderobertis", name: "onevcs") {
name
issues { edges { node { title } } }
}
}
}
"#;
const NO_PAGE_SIZE_ANYWHERE_NODES: u64 = 0;
#[test]
fn a_document_with_no_page_size_costs_nothing_here() {
assert_eq!(
node_count(NO_PAGE_SIZE_ANYWHERE, &no_variables()),
Ok(NO_PAGE_SIZE_ANYWHERE_NODES)
);
}
const SHORTHAND_OPERATION: &str = r#"
{
viewer {
repositories(first: 3) { edges { node { name } } }
}
}
"#;
const SHORTHAND_OPERATION_NODES: u64 = 3;
#[test]
fn a_shorthand_operation_is_counted() {
assert_eq!(
node_count(SHORTHAND_OPERATION, &no_variables()),
Ok(SHORTHAND_OPERATION_NODES)
);
}
const MUTATION_OPERATION: &str = r#"
mutation AddComment($body: String!) {
addComment(input: { subjectId: "MDU6SXNzdWUx", body: $body }) {
commentEdge {
node {
reactions(first: 5) { nodes { content } }
}
}
}
}
"#;
const MUTATION_OPERATION_NODES: u64 = 5;
#[test]
fn a_mutation_is_counted_and_an_unreferenced_variable_need_not_be_bound() {
assert_eq!(
node_count(MUTATION_OPERATION, &no_variables()),
Ok(MUTATION_OPERATION_NODES)
);
}
const SUBSCRIPTION_OPERATION: &str = r#"
subscription WatchIssues {
issueEvents {
issue {
comments(first: 8) { nodes { bodyHTML } }
}
}
}
"#;
const SUBSCRIPTION_OPERATION_NODES: u64 = 8;
#[test]
fn a_subscription_is_counted() {
assert_eq!(
node_count(SUBSCRIPTION_OPERATION, &no_variables()),
Ok(SUBSCRIPTION_OPERATION_NODES)
);
}
const UNUSED_FRAGMENT: &str = r#"
query {
viewer {
repositories(first: 4) { edges { node { name } } }
}
}
fragment Unused on Repository {
issues(first: 100) { edges { node { title } } }
}
"#;
const UNUSED_FRAGMENT_NODES: u64 = 4;
#[test]
fn a_fragment_that_is_never_spread_is_not_counted() {
assert_eq!(
node_count(UNUSED_FRAGMENT, &no_variables()),
Ok(UNUSED_FRAGMENT_NODES)
);
}
const POINT_NESTED_PATH: &str = r#"
query {
viewer {
repositories(first: 100) {
edges {
node {
issues(first: 100) {
edges {
node {
comments(first: 10) { nodes { bodyHTML } }
}
}
}
}
}
}
}
}
"#;
const POINT_NESTED_PATH_AGGREGATE: u64 = 10_101;
const POINT_NESTED_PATH_POINTS: u64 = 101;
const POINT_NESTED_PATH_NODES: u64 = 110_100;
#[test]
fn requests_multiply_down_a_nested_path() {
assert_eq!(
point_aggregate(POINT_NESTED_PATH, &no_variables()),
Ok(POINT_NESTED_PATH_AGGREGATE)
);
assert_eq!(
point_cost(POINT_NESTED_PATH, &no_variables()),
Ok(POINT_NESTED_PATH_POINTS)
);
assert_eq!(
node_count(POINT_NESTED_PATH, &no_variables()),
Ok(POINT_NESTED_PATH_NODES)
);
}
const POINT_SIBLING_PATHS: &str = r#"
query {
viewer {
repositories(first: 100) {
edges {
node {
issues(first: 20) { edges { node { title } } }
pullRequests(first: 30) { edges { node { title } } }
}
}
}
}
}
"#;
const POINT_SIBLING_PATHS_AGGREGATE: u64 = 201;
const POINT_SIBLING_PATHS_POINTS: u64 = 2;
const POINT_MAXIMUM_ACROSS_SIBLINGS: u64 = 101;
#[test]
fn requests_sum_across_sibling_paths() {
assert_eq!(
point_aggregate(POINT_SIBLING_PATHS, &no_variables()),
Ok(POINT_SIBLING_PATHS_AGGREGATE)
);
assert_ne!(
point_aggregate(POINT_SIBLING_PATHS, &no_variables()),
Ok(POINT_MAXIMUM_ACROSS_SIBLINGS),
"sibling connections must sum, not compete"
);
assert_eq!(
point_cost(POINT_SIBLING_PATHS, &no_variables()),
Ok(POINT_SIBLING_PATHS_POINTS)
);
}
const LEAF_PAGE_OF_ONE: &str = r#"
query {
viewer {
repositories(first: 100) {
edges {
node {
issues(first: 100) {
edges {
node {
comments(first: 1) { nodes { bodyHTML } }
}
}
}
}
}
}
}
}
"#;
const LEAF_PAGE_OF_A_HUNDRED: &str = r#"
query {
viewer {
repositories(first: 100) {
edges {
node {
issues(first: 100) {
edges {
node {
comments(first: 100) { nodes { bodyHTML } }
}
}
}
}
}
}
}
}
"#;
const LEAF_PAGE_AGGREGATE: u64 = 10_101;
const LEAF_PAGE_POINTS: u64 = 101;
const LEAF_PAGE_OF_ONE_NODES: u64 = 20_100;
const LEAF_PAGE_OF_A_HUNDRED_NODES: u64 = 1_010_100;
#[test]
fn a_connections_own_page_size_does_not_change_what_it_costs() {
for document in [LEAF_PAGE_OF_ONE, LEAF_PAGE_OF_A_HUNDRED] {
assert_eq!(
point_aggregate(document, &no_variables()),
Ok(LEAF_PAGE_AGGREGATE)
);
assert_eq!(point_cost(document, &no_variables()), Ok(LEAF_PAGE_POINTS));
}
assert_eq!(
node_count(LEAF_PAGE_OF_ONE, &no_variables()),
Ok(LEAF_PAGE_OF_ONE_NODES)
);
assert_eq!(
node_count(LEAF_PAGE_OF_A_HUNDRED, &no_variables()),
Ok(LEAF_PAGE_OF_A_HUNDRED_NODES)
);
assert_ne!(LEAF_PAGE_OF_ONE_NODES, LEAF_PAGE_OF_A_HUNDRED_NODES);
}
const AGGREGATE_OF_EXACTLY_150: &str = r#"
query {
viewer {
repositories(first: 74) {
edges { node { issues(first: 5) { nodes { title } } } }
}
followers(first: 74) {
edges { node { gists(first: 5) { nodes { name } } } }
}
}
}
"#;
const AGGREGATE_OF_149: &str = r#"
query {
viewer {
repositories(first: 74) {
edges { node { issues(first: 5) { nodes { title } } } }
}
followers(first: 73) {
edges { node { gists(first: 5) { nodes { name } } } }
}
}
}
"#;
const EXACTLY_150_AGGREGATE: u64 = 150;
const EXACTLY_150_POINTS: u64 = 2;
const JUST_UNDER_AGGREGATE: u64 = 149;
const JUST_UNDER_POINTS: u64 = 1;
#[test]
fn the_aggregate_rounds_to_the_nearest_point_with_ties_away_from_zero() {
assert_eq!(
point_aggregate(AGGREGATE_OF_EXACTLY_150, &no_variables()),
Ok(EXACTLY_150_AGGREGATE)
);
assert_eq!(
point_cost(AGGREGATE_OF_EXACTLY_150, &no_variables()),
Ok(EXACTLY_150_POINTS)
);
assert_eq!(
point_aggregate(AGGREGATE_OF_149, &no_variables()),
Ok(JUST_UNDER_AGGREGATE)
);
assert_eq!(
point_cost(AGGREGATE_OF_149, &no_variables()),
Ok(JUST_UNDER_POINTS)
);
}
#[test]
fn a_document_with_no_connection_still_costs_githubs_minimum_of_one() {
assert_eq!(
point_aggregate(NO_PAGE_SIZE_ANYWHERE, &no_variables()),
Ok(0)
);
assert_eq!(point_cost(NO_PAGE_SIZE_ANYWHERE, &no_variables()), Ok(1));
}
#[test]
fn a_single_connection_resolved_once_costs_one() {
assert_eq!(point_aggregate(SHORTHAND_OPERATION, &no_variables()), Ok(1));
assert_eq!(point_cost(SHORTHAND_OPERATION, &no_variables()), Ok(1));
let wide = r#"
{
viewer {
repositories(first: 100) { edges { node { name } } }
}
}
"#;
assert_eq!(point_aggregate(wide, &no_variables()), Ok(1));
assert_eq!(point_cost(wide, &no_variables()), Ok(1));
}
#[test]
fn a_spread_fragments_connections_are_aggregated_at_the_spreads_multiplier() {
assert_eq!(point_aggregate(SPREAD_FRAGMENT, &no_variables()), Ok(11));
assert_eq!(point_cost(SPREAD_FRAGMENT, &no_variables()), Ok(1));
assert_eq!(point_aggregate(NESTED_SPREADS, &no_variables()), Ok(7));
assert_eq!(point_aggregate(UNUSED_FRAGMENT, &no_variables()), Ok(1));
}
#[test]
fn a_page_size_variable_moves_the_aggregate_the_way_it_moves_the_multiplier() {
for (size, aggregate, nodes) in [
(3u32, 4u64, VARIABLE_SPENT_TWICE_AT_3),
(5, 6, VARIABLE_SPENT_TWICE_AT_5),
(9, 10, VARIABLE_SPENT_TWICE_AT_9),
] {
let variables = page(size);
assert_eq!(
point_aggregate(VARIABLE_SPENT_TWICE, &variables),
Ok(aggregate)
);
assert_eq!(node_count(VARIABLE_SPENT_TWICE, &variables), Ok(nodes));
assert_eq!(point_cost(VARIABLE_SPENT_TWICE, &variables), Ok(1));
}
}
#[test]
fn an_inline_fragments_connections_are_aggregated_against_its_parent() {
assert_eq!(
point_aggregate(INLINE_FRAGMENT_UNION, &no_variables()),
Ok(11)
);
assert_eq!(point_cost(INLINE_FRAGMENT_UNION, &no_variables()), Ok(1));
}
#[test]
fn a_mutation_and_a_subscription_are_priced_like_a_query() {
assert_eq!(point_aggregate(MUTATION_OPERATION, &no_variables()), Ok(1));
assert_eq!(point_cost(MUTATION_OPERATION, &no_variables()), Ok(1));
assert_eq!(
point_aggregate(SUBSCRIPTION_OPERATION, &no_variables()),
Ok(1)
);
assert_eq!(point_cost(SUBSCRIPTION_OPERATION, &no_variables()), Ok(1));
}
const PAGE_ARGUMENT_AGGREGATE: u64 = 26;
#[test]
fn last_and_first_and_literal_and_variable_all_cost_the_same() {
for (document, variables) in [
(OUTER_FIRST_LITERAL, no_variables()),
(OUTER_LAST_LITERAL, no_variables()),
(OUTER_FIRST_VARIABLE, page(25)),
(OUTER_LAST_VARIABLE, page(25)),
] {
assert_eq!(
point_aggregate(document, &variables),
Ok(PAGE_ARGUMENT_AGGREGATE)
);
assert_eq!(point_cost(document, &variables), Ok(1));
}
}
#[test]
fn a_field_supplying_both_first_and_last_is_priced_at_the_larger() {
assert_eq!(point_aggregate(BOTH_FIRST_AND_LAST, &no_variables()), Ok(1));
assert_eq!(point_cost(BOTH_FIRST_AND_LAST, &no_variables()), Ok(1));
assert_eq!(
point_aggregate(BOTH_FIRST_AND_LAST_NESTED, &no_variables()),
Ok(BOTH_FIRST_AND_LAST_NESTED_AGGREGATE)
);
assert_ne!(
point_aggregate(BOTH_FIRST_AND_LAST_NESTED, &no_variables()),
Ok(4)
);
assert_eq!(
node_count(BOTH_FIRST_AND_LAST_NESTED, &no_variables()),
Ok(BOTH_FIRST_AND_LAST_NESTED_NODES)
);
}