apiVersion: v1
kind: ConfigMap
metadata:
  name: "{{ .Release.Name }}-test-script"
  labels:
    helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
    app.kubernetes.io/managed-by: {{ .Release.Service }}
    app.kubernetes.io/instance: {{ .Release.Name | quote }}
    app.kubernetes.io/name: {{ template "neo4j.name" . }}
    app.kubernetes.io/component: tester
  annotations:
    "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
data:
  run.sh: |-
    export PATH=/usr/bin:$PATH
    export host="{{ template "neo4j.fullname" . }}.$NAMESPACE.svc.{{ .Values.clusterDomain }}"
    export replica_host="{{ template "neo4j.fullname" . }}.$NAMESPACE.svc.{{ .Values.clusterDomain }}"
    echo "HOST $host"
    # This endpoint proves availability of the overall service
    export endpoint="http://$host:$PORT_HTTP"
    echo "ENDPOINT $endpoint"
    # Mounted secret
    export NEO4J_SECRETS_PASSWORD=$(cat /secret/neo4j-password)
    export auth="neo4j:${NEO4J_SECRETS_PASSWORD}"
    echo "AUTH $auth"
    echo "CORES $CORES"
    echo "RRs $READ_REPLICAS"

    # When test resources are deployed cluster hasn't had a chance to form yet.
    # This polls in a loop waiting for cluster to become available, and gives up/fails
    # tests if it doesn't work within attempts.
    attempt=0
    attempts=5

    while true; do
      attempt=$[$attempt + 1]
      /usr/bin/curl -s -I $endpoint/ | grep "200 OK"
      if [ $? -eq 0 ] ; then
        echo "✔️ Neo4j is up at attempt $attempt; HTTP port $PORT_HTTP"
        break
      fi

      if [ $attempt -ge "$attempts" ]; then
        echo "❌ REST API seems not to be coming up, giving up after $attempts attempts"
        exit 1
      fi

      echo "Sleeping; not up yet after $attempt attempts"
      sleep 5
    done

    # Pass index ID to get hostname for that pod.
    function core_hostname {
      # Example: a-neo4j-core-0.a-neo4j.default.svc.cluster.local
      echo "{{ template "neo4j.core.fullname" . }}-$1.{{ template "neo4j.fullname" . }}.$NAMESPACE.svc.{{ .Values.clusterDomain }}"
    }

    function replica_hostname {      
      echo "{{ template "neo4j.replica.fullname" . }}-$1.{{ template "neo4j.replica.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}"
    }

    test_index=0

    function succeed {
      echo "✔️  Test $test_index: $1"
      test_index=$[$test_index + 1]
    }

    function fail {
      echo "❌ Test $test_index: $1"
      echo "Additional information: " "$2"
      exit 1
    }

    function cypher {
      # Use routing driver by default, send query wherever.
      DEFAULT_ENDPOINT="neo4j://$host:$PORT_BOLT"

      # If caller specified, use a specific endpoint to route a query to just one node.
      ENDPOINT=${2:-$DEFAULT_ENDPOINT}

      echo "$1" | /usr/bin/cypher-shell -u neo4j -a "$ENDPOINT" -p "$NEO4J_SECRETS_PASSWORD"
    }

    function runtest {
      # Use routing driver by default, send query wherever.
      DEFAULT_ENDPOINT="neo4j://$host:$PORT_BOLT"

      # If caller specified, use a specific endpoint to route a query to just one node.
      ENDPOINT=${3:-$DEFAULT_ENDPOINT}

      echo "Running $1 against $ENDPOINT"
      output=$(cypher "$2" "$3")

      if [ $? -eq 0 ] ; then  
        succeed "$1"
      else
        echo "Last output -- $output"
        fail "$1" "$output"
      fi
    }

    function check_secret_config_test {
       ENDPOINT=$1
       getconfig="call dbms.listConfig() yield name, value where name='dbms.transaction.concurrent.maximum' return value;"
       res=$(cypher "$getconfig" $ENDPOINT)

       echo $res | grep '"100"'
       if [ $? -eq 0 ] ; then
         succeed "$test"
       else
         fail "$test" "Expected it to be set to 0 - Output of config was $res"
       fi
    }

    # At this point the service endpoint proves that at least one host is up.
    # Provide just a bit more time for all of them to finish coming up because we'll
    # be testing them individually.
    echo "Waiting for formation to finish"
    attempt=0
    attempts=100
    while true; do
      attempt=$[$attempt + 1]
      cypher "RETURN 1;"
      if [ $? -eq 0 ] ; then
        echo "✔️ Neo4j BOLT is up at attempt $attempt; BOLT port $PORT_BOLT"
        break
      fi

      if [ $attempt -ge "$attempts" ]; then 
        echo "❌ BOLT API seems not to be coming up, giving up after $attempts attempts"
        exit 1
      fi

      echo "Sleeping; bolt not up yet after $attempt attempts"
      sleep 5
    done

    echo "All pre-requisites met, beginning main testing"

    echo "Basic topology upfront"
    cypher "CALL dbms.cluster.overview();"

    runtest "Bolt is available, port $PORT_BOLT"               "RETURN 'yes';"
    runtest "Basic read queries"                               "MATCH (n) RETURN COUNT(n);"
    runtest "Cluster accepts writes"                           'CREATE (t:TestNode) RETURN count(t);'

    # Clustering tests...
    {{- if not .Values.core.standalone }}
    runtest "Database is in clustered mode"                    "CALL dbms.cluster.overview();" 

    # Data from server on cluster topology.
    topology=$(cypher "CALL dbms.cluster.overview();")
    echo "TOPOLOGY $topology"

    # LEADERS
    leaders=$(echo $topology | grep 'system: "LEADER"' | wc -l)
    test="Cluster has 1 system leader"
    if [ $leaders -eq 1 ] ; then
      succeed "$test"
    else
      fail "$test" "$leaders leaders"
    fi

    # FOLLOWERS
    followers=$(echo $topology | grep -o 'system: "FOLLOWER"' | wc -l)
    test="Cluster has 1-CORES followers"
    if [ $followers -eq $((CORES-1)) ] ; then
      succeed "$test"
    else
      fail "$test" "$followers followers"
    fi

    # REPLICAS
    read_replicas=$(echo $topology | grep -o 'system: "READ_REPLICA"' | wc -l)
    test="Cluster has $READ_REPLICAS read replicas"
    if [ $read_replicas -eq $READ_REPLICAS ] ; then
      succeed "$test"
    else
      fail "$test" "$read_replicas replicas"
    fi

    # Each core is individually up and configured.
    for id in $(seq 0 $((CORES - 1))); do
      core_host=$(core_hostname $id)
      core_endpoint="bolt://$core_host:$PORT_BOLT"

      test="Core host $id of $CORES -- $core_endpoint is available"
      runtest "$test" "MATCH (n) RETURN COUNT(n);" "$core_endpoint"

      test="Core host $id of $CORES -- $core_endpoint has APOC installed correctly"
      runtest "$test" "RETURN apoc.version();" "$core_endpoint"
    done

    # Replicas are up and configured.
    if [ "$READ_REPLICAS" -gt 0 ]; then 
      replica_endpoint="bolt://$replica_host:$PORT_BOLT"
      test="Replica host -- $replica_endpoint is available"
      runtest "$test" "MATCH (n) RETURN COUNT(n);" "$replica_endpoint"
    fi
    {{- end }} # If database in clustered mode

    # Test for data replication.
    runtest "Sample canary write" 'CREATE (c:Canary) RETURN count(c);'
    echo "Sleeping a few seconds to permit replication"
    sleep 5

    {{- if not .Values.core.standalone }}
    # Check each core, count the canary writes. They should all agree.
    # We don't have to do this test for standalone, and note that above we already verified writes.
    for id in $(seq 0 $((CORES - 1))); do
      core_host=$(core_hostname $id)
      # Use bolt driver, not routing driver, to ensure that test verifies data
      # exists on this host.
      core_endpoint="bolt://$core_host:$PORT_BOLT"
      test="Core host $id has the canary write"
      result=$(cypher "MATCH (c:Canary) WITH count(c) as x where x = 1 RETURN x;" "$core_endpoint")
      exit_code=$?
      if [ $exit_code -eq 0 ] ; then
        # Check that the data is there.
        found_results=$(echo "$result" | grep -o 1 | wc -l)

        if [ $found_results -eq 1 ] ; then
          succeed "$test"
        else 
          fail "$test" "Canary read did not return data -- $found_results found results from $result"
        fi
      else
        fail "$test" "Canary read failed to execute -- exit code $exit_code / RESULT -- $result"
      fi

      test="Core host $id has the k8s secret configuration key"
      check_secret_config_test "$core_endpoint"
    done

    echo "Now testing read replicas"

    for id in $(seq 0 $((READ_REPLICAS - 1))); do
      replica=$(replica_hostname $id)
      replica_ep="bolt://$replica:$PORT_BOLT"
      test "Replica host $id has the canary write"
      result=$(cypher "MATCH (c:Canary) WITH count(c) as x where x = 1 RETURN x;" "$replica_ep")
      exit_code=$?
      if [ $exit_code -eq 0 ] ; then
        # Check that the data is there.
        found_results=$(echo "$result" | grep -o 1 | wc -l)

        if [ $found_results -eq 1 ] ; then
          succeed "$test"
        else 
          fail "$test" "Canary read did not return data -- $found_results found results from $result"
        fi
      else
        fail "$test" "Canary read failed to execute -- exit code $exit_code / RESULT -- $result"
      fi

      test="Replica host $id of $READ_REPLICAS -- $replica_ep has APOC installed correctly"
      runtest "$test" "RETURN apoc.version();" "$replica_ep"

      test="Replica host $id of $READ_REPLICAS has the k8s secret config key"
      check_secret_config_test "$replica_ep"
    done
    {{- end }}

    echo "All good; testing completed"
    exit 0
