From eca96ad37d89b442ae4e0463ec8f7a5bee3007f1 Mon Sep 17 00:00:00 2001
From: Benjamin Brahmer <info@b-brahmer.de>
Date: Sun, 29 May 2022 11:25:38 +0200
Subject: [PATCH] Enable API testing (#1699)

Enable API testing with local php server.

This adds many tests for API v1.2, more still possible.
Which increased the quality of news already.

Signed-off-by: Benjamin Brahmer <info@b-brahmer.de>
---
 .github/workflows/api-integration-tests.yml   |  24 ++-
 .gitignore                                    |   3 +
 .gitmodules                                   |   6 +
 tests/api/feeds.bats                          | 137 ++++++++++++++++++
 tests/api/folders.bats                        |  89 ++++++++++++
 tests/api/helpers/settings.bash               |   4 +
 tests/api/items.bats                          |  66 +++++++++
 tests/{integration => command}/explore.bats   |   0
 tests/{integration => command}/feeds.bats     |   0
 tests/{integration => command}/folders.bats   |   0
 .../helpers/settings.bash                     |   0
 tests/{integration => command}/items.bats     |   0
 tests/{integration => command}/opml.bats      |   0
 tests/test_helper/bats-assert                 |   1 +
 tests/test_helper/bats-support                |   1 +
 15 files changed, 326 insertions(+), 5 deletions(-)
 create mode 100644 .gitmodules
 create mode 100644 tests/api/feeds.bats
 create mode 100644 tests/api/folders.bats
 create mode 100644 tests/api/helpers/settings.bash
 create mode 100644 tests/api/items.bats
 rename tests/{integration => command}/explore.bats (100%)
 rename tests/{integration => command}/feeds.bats (100%)
 rename tests/{integration => command}/folders.bats (100%)
 rename tests/{integration => command}/helpers/settings.bash (100%)
 rename tests/{integration => command}/items.bats (100%)
 rename tests/{integration => command}/opml.bats (100%)
 create mode 160000 tests/test_helper/bats-assert
 create mode 160000 tests/test_helper/bats-support

diff --git a/.github/workflows/api-integration-tests.yml b/.github/workflows/api-integration-tests.yml
index c9a1d4cc9..117e7af49 100644
--- a/.github/workflows/api-integration-tests.yml
+++ b/.github/workflows/api-integration-tests.yml
@@ -33,11 +33,11 @@ jobs:
         database: ['sqlite', 'pgsql', 'mysql']
         experimental: [false]
         include:
-          - php-versions: 8.0
+          - php-versions: '8.0'
             nextcloud: pre-release
             database: sqlite
             experimental: true
-          - php-versions: 8.1
+          - php-versions: '8.1'
             nextcloud: pre-release
             database: sqlite
             experimental: true
@@ -48,6 +48,8 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v3
+        with:
+          submodules: recursive
 
       - name: Setup PHP
         uses: shivammathur/setup-php@v2
@@ -56,8 +58,8 @@ jobs:
           extensions: pdo_sqlite,pdo_mysql,pdo_pgsql,gd,zip
           coverage: none
 
-      - name: Setup BATS
-        uses: mig4/setup-bats@v1.2.0
+      - name: Setup BATS & httpie
+        run: sudo apt-get install -y bats httpie
 
       ### MySQL specific setup
       - name: Setup mysql
@@ -108,6 +110,17 @@ jobs:
           check-code: false
           force: ${{ matrix.experimental }}
 
+      - name: Run API tests
+        working-directory: ../server
+        run: |
+          php -S localhost:8080 &> /tmp/webserver.log &
+          sleep 2
+                  
+          bats apps/news/tests/api
+
+          # Kill php server
+          kill %1
+
       - name: Setup problem matchers for PHP
         run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
 
@@ -118,7 +131,7 @@ jobs:
 
       - name: Functional tests
         working-directory: ../server
-        run: bats apps/news/tests/integration
+        run: bats apps/news/tests/command
 
       - name: Prep PHP tests
         working-directory: ../server/apps/news
@@ -127,3 +140,4 @@ jobs:
       - name: Feed tests
         working-directory: ../server/apps/news
         run: make feed-test
+
diff --git a/.gitignore b/.gitignore
index 44cf6f335..7f9ed3550 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,9 @@ js/*.xml
 .phpunit.result.cache
 site/
 
+#bats
+tests/api/helpers/settings-override.bash
+
 # python
 PKG-INFO
 *pyc
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..d74af0778
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "tests/test_helper/bats-support"]
+	path = tests/test_helper/bats-support
+	url = https://github.com/bats-core/bats-support.git
+[submodule "tests/test_helper/bats-assert"]
+	path = tests/test_helper/bats-assert
+	url = https://github.com/bats-core/bats-assert.git
diff --git a/tests/api/feeds.bats b/tests/api/feeds.bats
new file mode 100644
index 000000000..054fd776a
--- /dev/null
+++ b/tests/api/feeds.bats
@@ -0,0 +1,137 @@
+#!/usr/bin/env bats
+
+setup() {
+  load "../test_helper/bats-support/load"
+  load "../test_helper/bats-assert/load"
+  load "helpers/settings"
+  
+  if test -f "tests/api/helpers/settings-override.bash"; then
+    load "helpers/settings-override"
+  fi
+}
+
+TESTSUITE="Feeds"
+
+teardown() {
+  # delete all feeds
+  ID_LIST=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+  for i in $ID_LIST; do
+    http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/feeds/$i
+  done
+
+  # delete all folders
+  ID_LIST=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/folders | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+  for i in $ID_LIST; do
+    http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/folders/$i
+  done
+}
+
+@test "[$TESTSUITE] Read empty" {
+  run http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds
+  
+  assert_output --partial "\"feeds\":[]"
+  assert_output --partial "\"starredCount\":0"
+}
+
+@test "[$TESTSUITE] Create new" {
+  # run is not working here.
+  output=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED | jq '.feeds | .[0].url')
+  
+  # self reference of feed is used here
+  assert_output '"https://nextcloud.com/feed/"'
+}
+
+@test "[$TESTSUITE] Create new inside folder" {
+  # create folder and store id
+  ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+
+  # run is not working here.
+  output=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED folderId=$ID | jq '.feeds | .[0].folderId')
+  
+  # check if ID matches
+  assert_output "$ID"
+}
+
+@test "[$TESTSUITE] Delete one" {
+  ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED | grep -Po '"id":\K([0-9]+)')
+  
+  run http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/feeds/$ID
+  
+  assert_output "[]"
+}
+
+@test "[$TESTSUITE] Move feed to different folder" {
+  # create folders and store ids
+  FIRST_FOLDER_ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+  SECCOND_FOLDER_ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+  
+  FEEDID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED folderId=$FIRST_FOLDER_ID | grep -Po '"id":\K([0-9]+)')
+  
+  # move feed, returns nothing
+  http --ignore-stdin -b -a ${user}:${user} PUT ${BASE_URLv1}/feeds/$FEEDID/move folderId=$SECCOND_FOLDER_ID
+
+  # run is not working here.
+  output=$(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds | jq '.feeds | .[0].folderId')
+  
+  # look for second folder id
+  assert_output "$SECCOND_FOLDER_ID"
+}
+
+@test "[$TESTSUITE] Move feed to root" {
+  # create folder and store id
+  FOLDER_ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+
+  FEEDID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED folderId=$FOLDER_ID | grep -Po '"id":\K([0-9]+)')
+  
+  # move feed to "null", returns nothing
+  http --ignore-stdin -b -a ${user}:${user} PUT ${BASE_URLv1}/feeds/$FEEDID/move folderId=null
+
+  # run is not working here.
+  output=$(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds | jq '.feeds | .[0].folderId')
+  
+  # new "folder" should be null
+  assert_output null
+}
+
+@test "[$TESTSUITE] Rename feed" {
+  # create feed and store id
+  FEEDID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED | grep -Po '"id":\K([0-9]+)')
+  
+  # rename feed, returns nothing
+  http --ignore-stdin -b -a ${user}:${user} PUT ${BASE_URLv1}/feeds/$FEEDID/rename feedTitle="Great Title"
+
+  # run is not working here.
+  output=$(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds | jq '.feeds | .[0].title')
+  
+  # Check if title matches
+  assert_output '"Great Title"'
+}
+
+@test "[$TESTSUITE] Mark all items as read" {
+  # create feed and store id
+  FEEDID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED | grep -Po '"id":\K([0-9]+)')
+  
+  ID_LIST=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items id=$FEEDID | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+
+  # get biggest item ID
+  max=${ID_LIST[0]}
+  for n in "${ID_LIST[@]}" ; do
+      ((n > max)) && max=$n
+  done
+  
+  # mark all items of feed as read, returns nothing
+  STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${user} PUT ${BASE_URLv1}/feeds/$FEEDID/read newestItemId="$max" 2>&1| grep HTTP/)
+
+  # collect unread status
+  unread=$(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items id=$FEEDID | grep -Po '"unread":\K((true)|(false))' | tr '\n' ' ')
+  
+  for n in "${unread[@]}" ; do
+      if $n
+      then
+        echo "Item was not marked as read"
+        echo $STATUS_CODE
+        false
+      fi
+  done
+}
+
diff --git a/tests/api/folders.bats b/tests/api/folders.bats
new file mode 100644
index 000000000..710b7d2a3
--- /dev/null
+++ b/tests/api/folders.bats
@@ -0,0 +1,89 @@
+#!/usr/bin/env bats
+
+setup() {
+  load "../test_helper/bats-support/load"
+  load "../test_helper/bats-assert/load"
+  load "helpers/settings"
+
+  if test -f "tests/api/helpers/settings-override.bash"; then
+    load "helpers/settings-override"
+  fi
+  
+}
+
+TESTSUITE="Folders"
+
+teardown() {
+  # delete all feeds
+  FEED_IDS=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+  for i in $FEED_IDS; do
+    http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/feeds/$i
+  done
+
+  # delete all folders
+  FOLDER_IDS=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/folders | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+  for i in $FOLDER_IDS; do
+    http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/folders/$i
+  done
+}
+
+@test "[$TESTSUITE] Read empty" {
+  run http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/folders
+  
+  assert_output --partial "\"folders\":[]"
+}
+
+@test "[$TESTSUITE] Create new" {
+  run http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER}
+  
+  assert_output --partial "\"name\":\"news-${BATS_SUITE_TEST_NUMBER}\","
+}
+
+@test "[$TESTSUITE] Delete folder" {
+  ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+
+  run http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/folders/$ID
+  
+  assert_output "[]"
+}
+
+@test "[$TESTSUITE] Rename folder" {
+  ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+
+  # Rename folder
+  http --ignore-stdin -b -a ${user}:${user} PUT ${BASE_URLv1}/folders/$ID name=rename-${BATS_SUITE_TEST_NUMBER}
+  
+  run http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/folders
+
+  assert_output --partial "\"name\":\"rename-${BATS_SUITE_TEST_NUMBER}\","
+}
+
+@test "[$TESTSUITE] Mark all items as read" {
+  # create folder and feed in folder
+  FOLDER_ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/folders name=news-${BATS_SUITE_TEST_NUMBER} | grep -Po '"id":\K([0-9]+)')
+  FEED_ID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED folderId=$FOLDER_ID | grep -Po '"id":\K([0-9]+)')
+  
+  ID_LIST=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items id=$FEEDID | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+
+  # get biggest item ID
+  max=${ID_LIST[0]}
+  for n in "${ID_LIST[@]}" ; do
+      ((n > max)) && max=$n
+  done
+  
+  # mark all items of feed as read, returns nothing
+  STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${user} PUT ${BASE_URLv1}/folders/$FOLDER_ID/read newestItemId="$max" 2>&1| grep HTTP/)
+
+  # collect unread status
+  unread=$(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items id=$FEEDID | grep -Po '"unread":\K((true)|(false))' | tr '\n' ' ')
+  
+  for n in "${unread[@]}" ; do
+      if $n
+      then
+        echo "Item was not marked as read"
+        echo $STATUS_CODE
+        false
+      fi
+  done
+}
+
diff --git a/tests/api/helpers/settings.bash b/tests/api/helpers/settings.bash
new file mode 100644
index 000000000..3b31fba4c
--- /dev/null
+++ b/tests/api/helpers/settings.bash
@@ -0,0 +1,4 @@
+user=admin
+NC_FEED="https://nextcloud.com/blog/static-feed/"
+HEISE_FEED="https://www.heise.de/rss/heise-atom.xml"
+BASE_URLv1="http://localhost:8080/index.php/apps/news/api/v1-2"
\ No newline at end of file
diff --git a/tests/api/items.bats b/tests/api/items.bats
new file mode 100644
index 000000000..349678966
--- /dev/null
+++ b/tests/api/items.bats
@@ -0,0 +1,66 @@
+#!/usr/bin/env bats
+
+setup() {
+  load "../test_helper/bats-support/load"
+  load "../test_helper/bats-assert/load"
+  load "helpers/settings"
+  
+  if test -f "tests/api/helpers/settings-override.bash"; then
+    load "helpers/settings-override"
+  fi
+}
+
+TESTSUITE="Items"
+
+teardown() {
+  # delete all feeds
+  FEED_IDS=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/feeds | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+  for i in $FEED_IDS; do
+    http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/feeds/$i
+  done
+
+  # delete all folders
+  FOLDER_IDS=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/folders | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+  for i in $FOLDER_IDS; do
+    http --ignore-stdin -b -a ${user}:${user} DELETE ${BASE_URLv1}/folders/$i
+  done
+}
+
+@test "[$TESTSUITE] Read empty" {
+  run http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items
+  
+  assert_output --partial "\"items\":[]"
+}
+
+@test "[$TESTSUITE] Read 5" {
+  http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED
+
+  ID_LIST=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items batchSize=5 | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+
+  output=${#ID_LIST[@]}
+  
+  assert_output --partial "5"
+}
+
+# TODO GET /items has more options that could be tested.
+
+@test "[$TESTSUITE] Check updated" {
+  FEEDID=$(http --ignore-stdin -b -a ${user}:${user} POST ${BASE_URLv1}/feeds url=$NC_FEED | grep -Po '"id":\K([0-9]+)')
+  ID_LIST=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items id=$FEEDID | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+
+  # get biggest item ID
+  max=${ID_LIST[0]}
+  for n in "${ID_LIST[@]}" ; do
+      ((n > max)) && max=$n
+  done
+  
+  SYNC_TIME=$(date +%s)
+
+  # mark all items of feed as read, returns nothing (other client marks items as read)
+  STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${user} PUT ${BASE_URLv1}/feeds/$FEEDID/read newestItemId="$max" 2>&1| grep HTTP/)
+
+  # client 2 checks for updates since last sync
+  UPDATED_ITEMS=($(http --ignore-stdin -b -a ${user}:${user} GET ${BASE_URLv1}/items/updated id=$FEEDID lastModified=$SYNC_TIME | grep -Po '"id":\K([0-9]+)' | tr '\n' ' '))
+
+  assert_equal ${#ID_LIST[@]} ${#UPDATED_ITEMS[@]}
+}
\ No newline at end of file
diff --git a/tests/integration/explore.bats b/tests/command/explore.bats
similarity index 100%
rename from tests/integration/explore.bats
rename to tests/command/explore.bats
diff --git a/tests/integration/feeds.bats b/tests/command/feeds.bats
similarity index 100%
rename from tests/integration/feeds.bats
rename to tests/command/feeds.bats
diff --git a/tests/integration/folders.bats b/tests/command/folders.bats
similarity index 100%
rename from tests/integration/folders.bats
rename to tests/command/folders.bats
diff --git a/tests/integration/helpers/settings.bash b/tests/command/helpers/settings.bash
similarity index 100%
rename from tests/integration/helpers/settings.bash
rename to tests/command/helpers/settings.bash
diff --git a/tests/integration/items.bats b/tests/command/items.bats
similarity index 100%
rename from tests/integration/items.bats
rename to tests/command/items.bats
diff --git a/tests/integration/opml.bats b/tests/command/opml.bats
similarity index 100%
rename from tests/integration/opml.bats
rename to tests/command/opml.bats
diff --git a/tests/test_helper/bats-assert b/tests/test_helper/bats-assert
new file mode 160000
index 000000000..397c73521
--- /dev/null
+++ b/tests/test_helper/bats-assert
@@ -0,0 +1 @@
+Subproject commit 397c735212bf1a06cfdd0cb7806c5a6ea79582bf
diff --git a/tests/test_helper/bats-support b/tests/test_helper/bats-support
new file mode 160000
index 000000000..3c8fadc50
--- /dev/null
+++ b/tests/test_helper/bats-support
@@ -0,0 +1 @@
+Subproject commit 3c8fadc5097c9acfc96d836dced2bb598e48b009