diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml index 94bc4dea..dc738914 100644 --- a/.github/workflows/deps.yml +++ b/.github/workflows/deps.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: NVD clojure - uses: jomco/nvd-clojure-action@v2 + uses: jomco/nvd-clojure-action@v3 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000..48d6dce9 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,49 @@ +name: Run e2e tests + +on: + workflow_dispatch: + push: + +jobs: + e2e: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: { path: "~/.m2", key: "${{ runner.os }}-m2" } + + - name: Start Redis + uses: supercharge/redis-github-action@1.4.0 + with: + redis-version: 6.2 + + - name: Run tests + env: + CLIENT_ID: ${{ secrets.CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + CLIENTS_INFO_PATH: ${{ secrets.CLIENTS_INFO_PATH }} + GATEWAY_PASSWORD: ${{ secrets.GATEWAY_PASSWORD }} + GATEWAY_ROOT_URL: ${{ secrets.GATEWAY_ROOT_URL }} + GATEWAY_USER: ${{ secrets.GATEWAY_USER }} + KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }} + KEYSTORE_JKS_B64: ${{ secrets.KEYSTORE_JKS_B64 }} + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEYSTORE: test/keystore.jks + OS_AUTH_URL: ${{ secrets.OS_AUTH_URL }} + OS_CONTAINER_NAME: ${{ secrets.OS_CONTAINER_NAME }} + OS_PASSWORD: ${{ secrets.OS_PASSWORD }} + OS_PROJECT_NAME: ${{ secrets.OS_PROJECT_NAME }} + OS_USERNAME: ${{ secrets.OS_USERNAME }} + RIO_READ_URL: ${{ secrets.RIO_READ_URL }} + RIO_RECIPIENT_OIN: ${{ secrets.RIO_RECIPIENT_OIN }} + RIO_SENDER_OIN: ${{ secrets.RIO_SENDER_OIN }} + RIO_UPDATE_URL: ${{ secrets.RIO_UPDATE_URL }} + SURF_CONEXT_CLIENT_ID: ${{ secrets.SURF_CONEXT_CLIENT_ID }} + SURF_CONEXT_CLIENT_SECRET: ${{ secrets.SURF_CONEXT_CLIENT_SECRET }} + SURF_CONEXT_INTROSPECTION_ENDPOINT: ${{ secrets.SURF_CONEXT_INTROSPECTION_ENDPOINT }} + TOKEN_ENDPOINT: ${{ secrets.TOKEN_ENDPOINT }} + TRUSTSTORE_JKS_B64: ${{ secrets.TRUSTSTORE_JKS_B64 }} + TRUSTSTORE_PASSWORD: ${{ secrets.TRUSTSTORE_PASSWORD }} + TRUSTSTORE: truststore.jks + run: lein test :e2e diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b9b8498..c0712887 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,8 +9,8 @@ jobs: NVD_API_TOKEN: ${{ secrets.NVD_API_TOKEN }} steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: { path: "~/.m2", key: "${{ runner.os }}-m2" } - name: Check dependency freshness @@ -23,8 +23,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: { path: "~/.m2", key: "${{ runner.os }}-m2" } - name: Run linters @@ -37,8 +37,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: { path: "~/.m2", key: "${{ runner.os }}-m2" } - name: Run tests @@ -81,8 +81,8 @@ jobs: # runs-on: ubuntu-latest # # steps: -# - uses: actions/checkout@v3 -# - uses: actions/cache@v3 +# - uses: actions/checkout@v4 +# - uses: actions/cache@v4 # with: { path: "~/.m2", key: "${{ runner.os }}-m2" } # # - name: Start Redis @@ -122,8 +122,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: { path: "~/.m2", key: "${{ runner.os }}-m2" } - name: Proof data specs @@ -133,8 +133,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: { path: "~/.m2", key: "${{ runner.os }}-m2" } - name: Run compile @@ -144,7 +144,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run docker build run: docker build -t eduhub-rio-mapper . diff --git a/src/nl/surf/eduhub_rio_mapper/cli.clj b/src/nl/surf/eduhub_rio_mapper/cli.clj index aab73ac5..4f572d47 100644 --- a/src/nl/surf/eduhub_rio_mapper/cli.clj +++ b/src/nl/surf/eduhub_rio_mapper/cli.clj @@ -198,7 +198,9 @@ response-type (and response-type (keyword response-type))] (assert (rio.loader/valid-get-types type)) (-> (when pagina {:pagina pagina}) - (assoc (if (= type "opleidingsrelatiesBijOpleidingseenheid") ::rio/opleidingscode ::ooapi/id) id + (assoc (if (rio.loader/aangeboden-opleiding-types type) + ::ooapi/id + ::rio/opleidingscode) id :response-type response-type ::rio/type type)))) diff --git a/src/nl/surf/eduhub_rio_mapper/job.clj b/src/nl/surf/eduhub_rio_mapper/job.clj index db50eeae..c0ec6952 100644 --- a/src/nl/surf/eduhub_rio_mapper/job.clj +++ b/src/nl/surf/eduhub_rio_mapper/job.clj @@ -39,8 +39,15 @@ :institution-schac-home institution-schac-home :institution-oin institution-oin :institution-name institution-name) - job (select-keys request [:action :args :institution-oin :institution-name :institution-schac-home - ::rio/aangeboden-opleiding-code ::rio/opleidingscode ::ooapi/type ::ooapi/id])] + job (select-keys request [:action + :args + :institution-oin + :institution-name + :institution-schac-home + ::rio/aangeboden-opleiding-code + ::rio/opleidingscode + ::ooapi/type + ::ooapi/id])] (logging/with-mdc log-context (log/infof "Started job %s, action %s, type %s, id %s" token action type id) (binding [*http-messages* (if http-logging-enabled (atom []) nil)] diff --git a/src/nl/surf/eduhub_rio_mapper/processing.clj b/src/nl/surf/eduhub_rio_mapper/processing.clj index 83144aeb..bc46980c 100644 --- a/src/nl/surf/eduhub_rio_mapper/processing.clj +++ b/src/nl/surf/eduhub_rio_mapper/processing.clj @@ -108,8 +108,7 @@ (defn- make-updater-mutate-rio-phase [{:keys [rio-config]}] (fn mutate-rio-phase [{:keys [job result eduspec]}] {:pre [(s/valid? ::Mutation/mutation-response result)]} - (logging/with-mdc - {:soap-action (:action result) :ooapi-id (::ooapi/id job)} + (logging/with-mdc {:soap-action (:action result) :ooapi-id (::ooapi/id job)} {:job job :eduspec eduspec :mutate-result (mutator/mutate! result rio-config)}))) (defn- make-updater-confirm-rio-phase [{:keys [resolver]} rio-config] diff --git a/src/nl/surf/eduhub_rio_mapper/rio/loader.clj b/src/nl/surf/eduhub_rio_mapper/rio/loader.clj index 8c33bbdc..e672cc03 100644 --- a/src/nl/surf/eduhub_rio_mapper/rio/loader.clj +++ b/src/nl/surf/eduhub_rio_mapper/rio/loader.clj @@ -45,12 +45,14 @@ (def aangeboden-opleiding-namen #{:aangebodenHOOpleidingsonderdeel :aangebodenHOOpleiding :aangebodenParticuliereOpleiding}) +;; NOTE: aangeboden opleidingen are referenced by OOAPI UID +(def aangeboden-opleiding-types #{aangeboden-opleiding + aangeboden-opleidingen-van-organisatie}) -(def valid-get-types #{aangeboden-opleiding - aangeboden-opleidingen-van-organisatie - opleidingseenheid - opleidingseenheden-van-organisatie - opleidingsrelaties-bij-opleidingseenheid}) +(def valid-get-types (into aangeboden-opleiding-types + #{opleidingseenheid + opleidingseenheden-van-organisatie + opleidingsrelaties-bij-opleidingseenheid})) (def schema "http://duo.nl/schema/DUO_RIO_Raadplegen_OnderwijsOrganisatie_V4") (def contract "http://duo.nl/contract/DUO_RIO_Raadplegen_OnderwijsOrganisatie_V4") @@ -184,8 +186,9 @@ (defn- response-handler-for-type [response-type type] (case response-type - :xml rio-xml-getter-response - :json rio-json-getter-response + :literal identity + :xml rio-xml-getter-response + :json rio-json-getter-response ;; If unspecified, use edn for relations and json for everything else (if (= type opleidingsrelaties-bij-opleidingseenheid) rio-relation-getter-response @@ -193,8 +196,10 @@ (defn find-opleidingseenheid [rio-code getter institution-oin] {:pre [rio-code]} - (-> (getter {::rio/type opleidingseenheid ::ooapi/id rio-code - :institution-oin institution-oin :response-type :xml}) + (-> (getter {::rio/type opleidingseenheid + ::rio/opleidingscode rio-code + :institution-oin institution-oin + :response-type :xml}) clj-xml/parse-str xml-seq (xml-utils/find-in-xmlseq #(when (opleidingseenheid-namen (:tag %)) %)))) @@ -244,28 +249,25 @@ ::rio/keys [type opleidingscode] :keys [institution-oin pagina response-type] :or {pagina 0}}] - {:pre [(or (not= type opleidingseenheid) - id)]} + {:pre [(or (and (aangeboden-opleiding-types type) id) + opleidingscode)]} (when-not (valid-get-types type) (throw (ex-info (str "Unexpected type: " type) - {:id id, - :opleidingscode opleidingscode, + {:id id + :opleidingscode opleidingscode :retryable? false}))) (when (and (= type opleidingseenheden-van-organisatie) - (not (valid-onderwijsbestuurcode? id))) - ;; WHOAA!! This is not a real OOAPI ID but a hack to allow - ;; command line to get opleidingseenheden. - (throw (ex-info (str "Type 'onderwijsbestuurcode' has ID invalid format: " id) - {:type type, - :opleidingscode opleidingscode + (not (valid-onderwijsbestuurcode? opleidingscode))) + (throw (ex-info (str "Type 'onderwijsbestuurcode' has ID invalid format: " opleidingscode) + {:type type :retryable? false}))) (let [soap-action (str "opvragen_" type) rio-sexp (condp = type ;; Command line only. opleidingseenheden-van-organisatie - [[:duo:onderwijsbestuurcode id] + [[:duo:onderwijsbestuurcode opleidingscode] ;; FIXME: this is not an opleidingscode! [:duo:pagina pagina]] ;; Command line only. @@ -280,9 +282,8 @@ [[:duo:aangebodenOpleidingCode id]] opleidingseenheid - [[:duo:opleidingseenheidcode id]])] - (logging/with-mdc - {:soap-action soap-action} + [[:duo:opleidingseenheidcode opleidingscode]])] + (logging/with-mdc {:soap-action soap-action} (let [xml (soap/prepare-soap-call soap-action rio-sexp (make-datamap institution-oin recipient-oin) diff --git a/src/nl/surf/eduhub_rio_mapper/rio/mutator.clj b/src/nl/surf/eduhub_rio_mapper/rio/mutator.clj index 431a3d4c..a81dc335 100644 --- a/src/nl/surf/eduhub_rio_mapper/rio/mutator.clj +++ b/src/nl/surf/eduhub_rio_mapper/rio/mutator.clj @@ -60,6 +60,7 @@ "A01160" ;; Externe identificatie niet uniek is niet recoverable dus zou niet moeten retryen "K01010" ;; 'propedeutischeFase' komt niet vaak genoeg voor als kenmerk "P01081" ;; Er bestaat al een opleidingseenheid met dezelfde eigenOpleidingseenheidSleutel + "N11030" ;; De einddatum van een instroomperiode moet na de begindatum liggen }) ;; Note: `P01810: Er bestaan nog verwijzingen naar de te verwijderen diff --git a/test/fixtures/remote-entities/courses/some.json b/test/fixtures/remote-entities/courses/some.json new file mode 100644 index 00000000..be18104f --- /dev/null +++ b/test/fixtures/remote-entities/courses/some.json @@ -0,0 +1,126 @@ +{ + "description" : [ { + "language" : "en-GB", + "value" : "Some course" + }, { + "language" : "nl-NL", + "value" : "Zomaar een cursus" + } ], + "validFrom" : "1994-05-15", + "validTo" : "2050-11-10", + "firstStartDate" : "1994-09-05", + "addresses" : [ { + "countryCode" : "NL", + "city" : "Manderveen", + "geoLocation" : { + "longitude" : 21, + "latitude" : 49 + }, + "addressType" : "teaching", + "streetNumber" : 84, + "postalCode" : "3465IQ", + "street" : "Kappeyne van de Coppellolaan", + "additional" : "" + } ], + "enrollment" : [ { + "language" : "en-GB", + "value" : "EN TRANSLATION: . . ." + }, { + "language" : "nl-NL", + "value" : "NL VERTALING: . . ." + } ], + "modeOfDelivery" : [ "distance-learning", "situated", "on campus" ], + "studyLoad" : { + "value" : 154, + "studyLoadUnit" : "ects" + }, + "fieldsOfStudy" : "0522", + "educationSpecification": "{{education-specifications/parent-course}}", + "name" : [ { + "language" : "en-GB", + "value" : "Some course" + }, { + "language" : "nl-NL", + "value" : "Zomaar een cursus" + } ], + "primaryCode" : { + "codeType" : "identifier", + "code" : "af28b74b-6a63-d901-ec5a-51b6f2e68ebc" + }, + "duration" : "P1DT30H4S", + "organization" : "cafecafe-cafe-cafe-cafe-cafecafecafe", + "assessment" : [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ], + "teachingLanguage" : "eng", + "level" : "undefined", + "consumers" : [ { + "alliances" : [ { + "name" : "ewuu", + "enrollmentForOwnStudents" : "broker", + "type" : "broadening", + "visibleForOwnStudents" : true, + "selection" : false, + "theme" : "Engineering, Manufacturing and Construction" + } ], + "consumerKey" : "eduxchange" + }, { + "consumerKey" : "rio", + "educationOffererCode" : "110A133", + "educationLocationCode" : "107X215", + "consentParticipationSTAP" : "permission_not_granted" + } ], + "link" : "https://hogeschool-van-manderveen.nl/courses/{{courses/some}}", + "otherCodes" : [ { + "codeType" : "identifier", + "code" : "cafecafe-cafe-cafe-cafe-cafecafecafe" + } ], + "coordinators" : [ "cafecafe-cafe-cafe-cafe-cafecafecafe" ], + "learningOutcomes" : [ [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ], [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ], [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ], [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ] ], + "admissionRequirements" : [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ], + "programs" : [ "cafecafe-cafe-cafe-cafe-cafecafecafe" ], + "resources" : [ "Definitions are provided on Brightspace. . All relevant information is in the workbook, supported by a reader (available at WUR-shop)." ], + "courseId" : "{{courses/some}}", + "abbreviation" : "MB", + "qualificationRequirements" : [ { + "language" : "en-GB", + "value" : ".." + }, { + "language" : "nl-NL", + "value" : ".." + } ] +} diff --git a/test/fixtures/remote-entities/courses/some/offerings.json b/test/fixtures/remote-entities/courses/some/offerings.json new file mode 100644 index 00000000..33ac76cb --- /dev/null +++ b/test/fixtures/remote-entities/courses/some/offerings.json @@ -0,0 +1,97 @@ +{ + "pageSize": 10, + "pageNumber": 1, + "items": [ + { + "pendingNumberStudents": 5, + "academicSession": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "addresses": [ + { + "countryCode": "NL", + "city": "Manderveen", + "geoLocation": { + "longitude": 21, + "latitude": 49 + }, + "addressType": "teaching", + "streetNumber": 84, + "postalCode": "3465IQ", + "street": "Kappeyne van de Coppellolaan", + "additional": "" + } + ], + "teachingLanguage": "nld", + "modeOfDelivery": [ + "situated", + "distance-learning" + ], + "enrollStartDate": "1994-09-05", + "offeringType": "course", + "resultExpected": false, + "minNumberStudents": 3, + "startDate": "1994-09-05", + "flexibleEntryPeriodStart": "1994-09-05", + "enrollEndDate": "1995-01-31", + "endDate": "1995-01-31", + "primaryCode": { + "codeType": "offeringCode", + "code": "cafecafe-cafe-cafe-cafe-cafecafecafe" + }, + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "name": [ + { + "language": "en-GB", + "value": "Some course offerings" + }, + { + "language": "nl-NL", + "value": "Zomaar een cursus aanbod" + } + ], + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "link": "https://hogeschool-van-manderveen.nl/offerings/cafecafe-cafe-cafe-cafe-cafecafecafe", + "course": "{{courses/some}}", + "otherCodes": [ + { + "codeType": "offeringCode", + "code": "cafecafe-cafe-cafe-cafe-cafecafecafe" + } + ], + "enrolledNumberStudents": 57, + "flexibleEntryPeriodEnd": "2026-01-31", + "consumers": [ + { + "alliances": [ + { + "name": "ewuu", + "enrollmentUrl": "https://www.eigen-inschrijf-url.nl/schrijf-hier-in" + } + ], + "consumerKey": "eduxchange" + }, + { + "consumerKey": "rio", + "requiredPermissionRegistration": "no", + "registrationStatus": "open" + } + ], + "offeringId": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "programOffering": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "maxNumberStudents": 137, + "abbreviation": "MB", + "resultValueType": "0-100" + } + ], + "hasPreviousPage": false, + "hasNextPage": false, + "totalPages": 1 +} diff --git a/test/fixtures/remote-entities/education-specifications/bad-type.json b/test/fixtures/remote-entities/education-specifications/bad-type.json new file mode 100644 index 00000000..12e0a5ec --- /dev/null +++ b/test/fixtures/remote-entities/education-specifications/bad-type.json @@ -0,0 +1,77 @@ +{ + "level": "bachelor", + "children": [], + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "educationSpecificationType": "badType", + "educationSpecificationId": "{{education-specifications/bad-type}}", + "validFrom": "1950-09-20", + "abbreviation": "1T", + "validTo": "2060-08-28", + "learningOutcomes": [ + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] + ], + "formalDocument": "diploma", + "sector": "higher professional education", + "name": [ + { + "language": "en-GB", + "value": "bad-type education specification" + }, + { + "language": "nl-NL", + "value": "bad-type education specification" + } + ], + "primaryCode": { + "codeType": "identifier", + "code": "{{education-specifications/bad-type}}" + }, + "link": "https://universiteit-van-boxtel.nl/education-specifications/{{education-specifications/bad-type}}", + "fieldsOfStudy": "0912", + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "levelOfQualification": "6", + "consumers": [ + { + "consumerKey": "rio", + "category": [ + "business_and_project_support", + "education", + "cross_sectoral", + "language_and_culture", + "technology_and_ict" + ] + } + ], + "studyLoad": { + "value": 93, + "studyLoadUnit": "sbu" + } +} diff --git a/test/fixtures/remote-entities/education-specifications/child.json b/test/fixtures/remote-entities/education-specifications/child-program.json similarity index 51% rename from test/fixtures/remote-entities/education-specifications/child.json rename to test/fixtures/remote-entities/education-specifications/child-program.json index 280ebf31..0b325848 100644 --- a/test/fixtures/remote-entities/education-specifications/child.json +++ b/test/fixtures/remote-entities/education-specifications/child-program.json @@ -1,9 +1,9 @@ { "level": "bachelor", - "parent": "{{education-specifications/parent}}", - "organization": "c388522c-ebc1-2889-1899-06a89e4f5091", + "parent": "{{education-specifications/parent-program}}", + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", "educationSpecificationType": "program", - "educationSpecificationId": "{{education-specifications/child}}", + "educationSpecificationId": "{{education-specifications/child-program}}", "validFrom": "2016-12-15", "abbreviation": "OT", "validTo": "2025-04-25", @@ -11,11 +11,11 @@ [ { "language": "en-GB", - "value": "EN TRANSLATION: algemene empathie voor levende wezens" + "value": "child program eduspec" }, { "language": "nl-NL", - "value": "NL VERTALING: algemene empathie voor levende wezens" + "value": "child program eduspec" } ] ], @@ -24,27 +24,27 @@ "name": [ { "language": "en-GB", - "value": "child education specification" + "value": "child-program education specification" }, { "language": "nl-NL", - "value": "child education specification" + "value": "child-program education specification" } ], "primaryCode": { "codeType": "identifier", - "code": "{{education-specifications/child}}" + "code": "{{education-specifications/child-program}}" }, - "link": "https://universiteit-van-boxtel.nl/education-specifications/{{education-specifications/child}}", + "link": "https://universiteit-van-boxtel.nl/education-specifications/{{education-specifications/child-program}}", "fieldsOfStudy": "0612", "description": [ { "language": "en-GB", - "value": "EN TRANSLATION: terrestrial systems. Examples include vegetation patterning, and dispersal by organisms, both at the global hydrological cycle contained in different watersheds in Valencia. The 4th semester revolves around two commonly asked questions: i) how can science inform the design tools." + "value": ".." }, { "language": "nl-NL", - "value": "NL VERTALING: terrestrial systems. Examples include vegetation patterning, and dispersal by organisms, both at the global hydrological cycle contained in different watersheds in Valencia. The 4th semester revolves around two commonly asked questions: i) how can science inform the design tools." + "value": ".." } ], "levelOfQualification": "6", diff --git a/test/fixtures/remote-entities/education-specifications/parent-course.json b/test/fixtures/remote-entities/education-specifications/parent-course.json new file mode 100644 index 00000000..8e6e8100 --- /dev/null +++ b/test/fixtures/remote-entities/education-specifications/parent-course.json @@ -0,0 +1,77 @@ +{ + "level": "bachelor", + "children": [], + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "educationSpecificationType": "course", + "educationSpecificationId": "{{education-specifications/parent-course}}", + "validFrom": "1950-09-20", + "abbreviation": "1T", + "validTo": "2060-08-28", + "learningOutcomes": [ + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] + ], + "formalDocument": "diploma", + "sector": "higher professional education", + "name": [ + { + "language": "en-GB", + "value": "parent-course education specification" + }, + { + "language": "nl-NL", + "value": "parent-course education specification" + } + ], + "primaryCode": { + "codeType": "identifier", + "code": "{{education-specifications/parent-course}}" + }, + "link": "https://universiteit-van-boxtel.nl/education-specifications/{{education-specifications/parent-course}}", + "fieldsOfStudy": "0912", + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "levelOfQualification": "6", + "consumers": [ + { + "consumerKey": "rio", + "category": [ + "business_and_project_support", + "education", + "cross_sectoral", + "language_and_culture", + "technology_and_ict" + ] + } + ], + "studyLoad": { + "value": 93, + "studyLoadUnit": "sbu" + } +} diff --git a/test/fixtures/remote-entities/education-specifications/parent-program.json b/test/fixtures/remote-entities/education-specifications/parent-program.json new file mode 100644 index 00000000..4616e37a --- /dev/null +++ b/test/fixtures/remote-entities/education-specifications/parent-program.json @@ -0,0 +1,77 @@ +{ + "level": "bachelor", + "children": ["{{education-specifications/child-program}}"], + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "educationSpecificationType": "program", + "educationSpecificationId": "{{education-specifications/parent-program}}", + "validFrom": "1950-09-20", + "abbreviation": "1T", + "validTo": "2060-08-28", + "learningOutcomes": [ + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] + ], + "formalDocument": "diploma", + "sector": "higher professional education", + "name": [ + { + "language": "en-GB", + "value": "parent-program education specification" + }, + { + "language": "nl-NL", + "value": "parent-program education specification" + } + ], + "primaryCode": { + "codeType": "identifier", + "code": "{{education-specifications/parent-program}}" + }, + "link": "https://universiteit-van-boxtel.nl/education-specifications/{{education-specifications/parent-program}}", + "fieldsOfStudy": "0912", + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "levelOfQualification": "6", + "consumers": [ + { + "consumerKey": "rio", + "category": [ + "business_and_project_support", + "education", + "cross_sectoral", + "language_and_culture", + "technology_and_ict" + ] + } + ], + "studyLoad": { + "value": 93, + "studyLoadUnit": "sbu" + } +} diff --git a/test/fixtures/remote-entities/education-specifications/parent.json b/test/fixtures/remote-entities/education-specifications/parent.json deleted file mode 100644 index 667921e6..00000000 --- a/test/fixtures/remote-entities/education-specifications/parent.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "level": "bachelor", - "children": ["{{education-specifications/child}}"], - "organization": "c388522c-ebc1-2889-1899-06a89e4f5091", - "educationSpecificationType": "program", - "educationSpecificationId": "{{education-specifications/parent}}", - "validFrom": "1950-09-20", - "abbreviation": "1T", - "validTo": "2060-08-28", - "learningOutcomes": [ - [ - { - "language": "en-GB", - "value": "EN TRANSLATION: nieuwe inzichten over het opdoen van kennis in een complexe wereld" - }, - { - "language": "nl-NL", - "value": "NL VERTALING: nieuwe inzichten over het opdoen van kennis in een complexe wereld" - } - ], - [ - { - "language": "en-GB", - "value": "EN TRANSLATION: nieuw talent op het gebied van programmeren" - }, - { - "language": "nl-NL", - "value": "NL VERTALING: nieuw talent op het gebied van programmeren" - } - ] - ], - "formalDocument": "diploma", - "sector": "higher professional education", - "name": [ - { - "language": "en-GB", - "value": "parent education specification" - }, - { - "language": "nl-NL", - "value": "parent education specification" - } - ], - "primaryCode": { - "codeType": "identifier", - "code": "{{education-specifications/parent}}" - }, - "link": "https://universiteit-van-boxtel.nl/education-specifications/{{education-specifications/parent}}", - "fieldsOfStudy": "0912", - "description": [ - { - "language": "en-GB", - "value": "EN TRANSLATION: There is a 12 credits course which offers student the opportunity to experience in the domain will be paid to the depletion of fossil resources to biomass resources for energy, raw materials and tree form. The course addresses the question if and how this relates to material struggles over natural resources and its relationship to economic and technological domains;- solving optimization problems is climate change. Within a theoretical stance to support making conscious study and career choices." - }, - { - "language": "nl-NL", - "value": "NL VERTALING: There is a 12 credits course which offers student the opportunity to experience in the domain will be paid to the depletion of fossil resources to biomass resources for energy, raw materials and tree form. The course addresses the question if and how this relates to material struggles over natural resources and its relationship to economic and technological domains;- solving optimization problems is climate change. Within a theoretical stance to support making conscious study and career choices." - } - ], - "levelOfQualification": "6", - "consumers": [ - { - "consumerKey": "rio", - "category": [ - "business_and_project_support", - "education", - "cross_sectoral", - "language_and_culture", - "technology_and_ict" - ] - } - ], - "studyLoad": { - "value": 93, - "studyLoadUnit": "sbu" - } -} diff --git a/test/fixtures/remote-entities/programs/bad-edu-location.json b/test/fixtures/remote-entities/programs/bad-edu-location.json new file mode 100644 index 00000000..f8f95401 --- /dev/null +++ b/test/fixtures/remote-entities/programs/bad-edu-location.json @@ -0,0 +1,190 @@ +{ + "fieldsOfStudy": "0612", + "teachingLanguage": "eng", + "studyLoad": { + "value": 155, + "studyLoadUnit": "ects" + }, + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "level": "nt2-2", + "educationSpecification": "{{education-specifications/parent-program}}", + "name": [ + { + "language": "en-GB", + "value": "bad-edu-location" + }, + { + "language": "nl-NL", + "value": "bad-edu-location" + } + ], + "sector": "secondary vocational education", + "validFrom": "2008-10-18", + "addresses": [ + { + "countryCode": "NL", + "city": "Morra", + "geoLocation": { + "longitude": 88, + "latitude": 0 + }, + "addressType": "teaching", + "streetNumber": 23, + "postalCode": "4184QU", + "street": "Tollenslaan", + "additional": "a" + } + ], + "programType": "program", + "primaryCode": { + "codeType": "crohoCreboCode", + "code": "60740" + }, + "modeOfDelivery": [ + "on campus", + "distance-learning", + "hybrid", + "online" + ], + "duration": "P1DT30H4S", + "qualificationAwarded": "Phd", + "link": "https://universiteit-van-morra.nl/programs/{{programs/some}}", + "coordinators": [ + "cafecafe-cafe-cafe-cafe-cafecafecafe" + ], + "otherCodes": [ + { + "codeType": "crohoCreboCode", + "code": "60740" + } + ], + "validTo": "2023-12-18", + "modeOfStudy": "dual training", + "assessment": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "levelOfQualification": "2", + "consumers": [ + { + "alliances": [ + { + "name": "lde", + "enrollmentForOwnStudents": "broker", + "type": "broadening", + "visibleForOwnStudents": false, + "selection": true, + "theme": "13" + } + ], + "consumerKey": "eduxchange" + }, + { + "educationOffererCode": "110A133", + "acceleratedRoute": "no_accelerated_route", + "consentParticipationSTAP": "permission_not_granted", + "deficiency": "deficiencies", + "studyChoiceCheck": "no_study_choice_check", + "consumerKey": "rio", + "requirementsActivities": "requirements", + "propaedeuticPhase": "no_propaedeutic_phase", + "educationLocationCode": "BAD-CODE" + } + ], + "programId": "{{programs/some}}", + "firstStartDate": "2010-01-31", + "resources": [ + "Lelieveldt, H. Course guide. Sage.Miles, M.", + "- lecture hand-outs and additional book chapters will be available online. Sage. Mourato (2006).", + "B. Practical manual Soil Pollution and Soil Protection. ." + ], + "qualificationRequirements": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "abbreviation": "TEC", + "learningOutcomes": [ + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] + ], + "admissionRequirements": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "enrollment": [ + { + "language": "en-GB", + "value": "EN TRANSLATION: . . ." + }, + { + "language": "nl-NL", + "value": "NL VERTALING: . . ." + } + ] +} diff --git a/test/fixtures/remote-entities/programs/bad-edu-location/offerings.json b/test/fixtures/remote-entities/programs/bad-edu-location/offerings.json new file mode 100644 index 00000000..b2ca6153 --- /dev/null +++ b/test/fixtures/remote-entities/programs/bad-edu-location/offerings.json @@ -0,0 +1,106 @@ +{ + "pageSize": 10, + "pageNumber": 1, + "hasPreviousPage": false, + "hasNextPage": false, + "totalPages": 1, + "items": [ + { + "offeringId": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "primaryCode": { + "codeType": "identifier", + "code": "1234qwe12" + }, + "offeringType": "component", + "academicSession": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "name": [ + { + "language": "en-GB", + "value": "Final written test for INFOMQNM for fall semseter 2020" + } + ], + "abbreviation": "Test-INFOMQNM-20FS", + "description": [ + { + "language": "en-GB", + "value": ".." + } + ], + "teachingLanguage": "nld", + "modeOfDelivery": [ + "situated" + ], + "maxNumberStudents": 200, + "enrolledNumberStudents": 150, + "pendingNumberStudents": 50, + "minNumberStudents": 15, + "resultExpected": true, + "resultValueType": "1-10", + "link": "https://osiris.uu.nl/osiris_student_uuprd/OnderwijsCatalogusZoekCursus.do#submitForm?cursuscode=INFOMQNM", + "otherCodes": [ + { + "codeType": "identifier", + "code": "1234qwe12" + } + ], + "consumers": [ + { + "consumerKey": "rio", + "explanationRequiredPermission": "Toestemming is vereist omdat we daarom vragen.", + "requiredPermissionRegistration": "yes", + "registrationStatus": "open" + } + ], + "ext": {}, + "startDate": "2019-08-21", + "endDate": "2023-06-15", + "enrollStartDate": "2019-05-01", + "enrollEndDate": "2019-08-01", + "flexibleEntryPeriodStart": "2019-08-24", + "flexibleEntryPeriodEnd": "2019-09-24", + "addresses": [ + { + "addressType": "postal", + "street": "Moreelsepark", + "streetNumber": "48", + "additional": [ + { + "language": "en-GB", + "value": "On the other side of the road" + } + ], + "postalCode": "3511 EP", + "city": "Utrecht", + "countryCode": "NL", + "geolocation": { + "latitude": 52.089123, + "longitude": 5.113337 + }, + "ext": {} + } + ], + "priceInformation": [ + { + "costType": "total costs", + "amount": "340.84", + "vatAmount": "40", + "amountWithoutVat": "300.84", + "currency": "EUR", + "displayAmount": [ + { + "language": "nl-NL", + "value": "€380,84" + }, + { + "language": "en-US", + "value": "$401.17" + } + ], + "ext": {} + } + ], + "program": "{{programs/bad-edu-location}}" + } + ], + "ext": {} +} diff --git a/test/fixtures/remote-entities/programs/bad-edu-offerer.json b/test/fixtures/remote-entities/programs/bad-edu-offerer.json new file mode 100644 index 00000000..598d252e --- /dev/null +++ b/test/fixtures/remote-entities/programs/bad-edu-offerer.json @@ -0,0 +1,190 @@ +{ + "fieldsOfStudy": "0612", + "teachingLanguage": "eng", + "studyLoad": { + "value": 155, + "studyLoadUnit": "ects" + }, + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "level": "nt2-2", + "educationSpecification": "{{education-specifications/parent-program}}", + "name": [ + { + "language": "en-GB", + "value": "bad-edu-offerer" + }, + { + "language": "nl-NL", + "value": "bad-edu-offerer" + } + ], + "sector": "secondary vocational education", + "validFrom": "2008-10-18", + "addresses": [ + { + "countryCode": "NL", + "city": "Morra", + "geoLocation": { + "longitude": 88, + "latitude": 0 + }, + "addressType": "teaching", + "streetNumber": 23, + "postalCode": "4184QU", + "street": "Tollenslaan", + "additional": "a" + } + ], + "programType": "program", + "primaryCode": { + "codeType": "crohoCreboCode", + "code": "60740" + }, + "modeOfDelivery": [ + "on campus", + "distance-learning", + "hybrid", + "online" + ], + "duration": "P1DT30H4S", + "qualificationAwarded": "Phd", + "link": "https://universiteit-van-morra.nl/programs/{{programs/some}}", + "coordinators": [ + "cafecafe-cafe-cafe-cafe-cafecafecafe" + ], + "otherCodes": [ + { + "codeType": "crohoCreboCode", + "code": "60740" + } + ], + "validTo": "2023-12-18", + "modeOfStudy": "dual training", + "assessment": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "levelOfQualification": "2", + "consumers": [ + { + "alliances": [ + { + "name": "lde", + "enrollmentForOwnStudents": "broker", + "type": "broadening", + "visibleForOwnStudents": false, + "selection": true, + "theme": "13" + } + ], + "consumerKey": "eduxchange" + }, + { + "educationOffererCode": "BAD-CODE", + "acceleratedRoute": "no_accelerated_route", + "consentParticipationSTAP": "permission_not_granted", + "deficiency": "deficiencies", + "studyChoiceCheck": "no_study_choice_check", + "consumerKey": "rio", + "requirementsActivities": "requirements", + "propaedeuticPhase": "no_propaedeutic_phase", + "educationLocationCode": "107X215" + } + ], + "programId": "{{programs/some}}", + "firstStartDate": "2010-01-31", + "resources": [ + "Lelieveldt, H. Course guide. Sage.Miles, M.", + "- lecture hand-outs and additional book chapters will be available online. Sage. Mourato (2006).", + "B. Practical manual Soil Pollution and Soil Protection. ." + ], + "qualificationRequirements": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "abbreviation": "TEC", + "learningOutcomes": [ + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] + ], + "admissionRequirements": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "enrollment": [ + { + "language": "en-GB", + "value": "EN TRANSLATION: . . ." + }, + { + "language": "nl-NL", + "value": "NL VERTALING: . . ." + } + ] +} diff --git a/test/fixtures/remote-entities/programs/bad-edu-offerer/offerings.json b/test/fixtures/remote-entities/programs/bad-edu-offerer/offerings.json new file mode 100644 index 00000000..5c735ff8 --- /dev/null +++ b/test/fixtures/remote-entities/programs/bad-edu-offerer/offerings.json @@ -0,0 +1,106 @@ +{ + "pageSize": 10, + "pageNumber": 1, + "hasPreviousPage": false, + "hasNextPage": false, + "totalPages": 1, + "items": [ + { + "offeringId": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "primaryCode": { + "codeType": "identifier", + "code": "1234qwe12" + }, + "offeringType": "component", + "academicSession": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "name": [ + { + "language": "en-GB", + "value": "bad-edu-offerer/offerings" + } + ], + "abbreviation": "Test-INFOMQNM-20FS", + "description": [ + { + "language": "en-GB", + "value": ".." + } + ], + "teachingLanguage": "nld", + "modeOfDelivery": [ + "situated" + ], + "maxNumberStudents": 200, + "enrolledNumberStudents": 150, + "pendingNumberStudents": 50, + "minNumberStudents": 15, + "resultExpected": true, + "resultValueType": "1-10", + "link": "https://osiris.uu.nl/osiris_student_uuprd/OnderwijsCatalogusZoekCursus.do#submitForm?cursuscode=INFOMQNM", + "otherCodes": [ + { + "codeType": "identifier", + "code": "1234qwe12" + } + ], + "consumers": [ + { + "consumerKey": "rio", + "explanationRequiredPermission": "Toestemming is vereist omdat we daarom vragen.", + "requiredPermissionRegistration": "yes", + "registrationStatus": "open" + } + ], + "ext": {}, + "startDate": "2019-08-21", + "endDate": "2023-06-15", + "enrollStartDate": "2019-05-01", + "enrollEndDate": "2019-08-01", + "flexibleEntryPeriodStart": "2019-08-24", + "flexibleEntryPeriodEnd": "2019-09-24", + "addresses": [ + { + "addressType": "postal", + "street": "Moreelsepark", + "streetNumber": "48", + "additional": [ + { + "language": "en-GB", + "value": "On the other side of the road" + } + ], + "postalCode": "3511 EP", + "city": "Utrecht", + "countryCode": "NL", + "geolocation": { + "latitude": 52.089123, + "longitude": 5.113337 + }, + "ext": {} + } + ], + "priceInformation": [ + { + "costType": "total costs", + "amount": "340.84", + "vatAmount": "40", + "amountWithoutVat": "300.84", + "currency": "EUR", + "displayAmount": [ + { + "language": "nl-NL", + "value": "€380,84" + }, + { + "language": "en-US", + "value": "$401.17" + } + ], + "ext": {} + } + ], + "program": "{{programs/bad-edu-offerer}}" + } + ], + "ext": {} +} diff --git a/test/fixtures/remote-entities/programs/some.json b/test/fixtures/remote-entities/programs/some.json new file mode 100644 index 00000000..63ef1d71 --- /dev/null +++ b/test/fixtures/remote-entities/programs/some.json @@ -0,0 +1,190 @@ +{ + "fieldsOfStudy": "0612", + "teachingLanguage": "eng", + "studyLoad": { + "value": 155, + "studyLoadUnit": "ects" + }, + "organization": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "level": "nt2-2", + "educationSpecification": "{{education-specifications/parent-program}}", + "name": [ + { + "language": "en-GB", + "value": "Some a program" + }, + { + "language": "nl-NL", + "value": "Zomaar een programma" + } + ], + "sector": "secondary vocational education", + "validFrom": "2008-10-18", + "addresses": [ + { + "countryCode": "NL", + "city": "Morra", + "geoLocation": { + "longitude": 88, + "latitude": 0 + }, + "addressType": "teaching", + "streetNumber": 23, + "postalCode": "4184QU", + "street": "Tollenslaan", + "additional": "a" + } + ], + "programType": "program", + "primaryCode": { + "codeType": "crohoCreboCode", + "code": "60740" + }, + "modeOfDelivery": [ + "on campus", + "distance-learning", + "hybrid", + "online" + ], + "duration": "P1DT30H4S", + "qualificationAwarded": "Phd", + "link": "https://universiteit-van-morra.nl/programs/{{programs/some}}", + "coordinators": [ + "cafecafe-cafe-cafe-cafe-cafecafecafe" + ], + "otherCodes": [ + { + "codeType": "crohoCreboCode", + "code": "60740" + } + ], + "validTo": "2023-12-18", + "modeOfStudy": "dual training", + "assessment": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "description": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "levelOfQualification": "2", + "consumers": [ + { + "alliances": [ + { + "name": "lde", + "enrollmentForOwnStudents": "broker", + "type": "broadening", + "visibleForOwnStudents": false, + "selection": true, + "theme": "13" + } + ], + "consumerKey": "eduxchange" + }, + { + "educationOffererCode": "110A133", + "acceleratedRoute": "no_accelerated_route", + "consentParticipationSTAP": "permission_not_granted", + "deficiency": "deficiencies", + "studyChoiceCheck": "no_study_choice_check", + "consumerKey": "rio", + "requirementsActivities": "requirements", + "propaedeuticPhase": "no_propaedeutic_phase", + "educationLocationCode": "107X215" + } + ], + "programId": "{{programs/some}}", + "firstStartDate": "2010-01-31", + "resources": [ + "Lelieveldt, H. Course guide. Sage.Miles, M.", + "- lecture hand-outs and additional book chapters will be available online. Sage. Mourato (2006).", + "B. Practical manual Soil Pollution and Soil Protection. ." + ], + "qualificationRequirements": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "abbreviation": "TEC", + "learningOutcomes": [ + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] + ], + "admissionRequirements": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ], + "enrollment": [ + { + "language": "en-GB", + "value": ".." + }, + { + "language": "nl-NL", + "value": ".." + } + ] +} diff --git a/test/fixtures/remote-entities/programs/some/offerings.json b/test/fixtures/remote-entities/programs/some/offerings.json new file mode 100644 index 00000000..e80a0975 --- /dev/null +++ b/test/fixtures/remote-entities/programs/some/offerings.json @@ -0,0 +1,106 @@ +{ + "pageSize": 10, + "pageNumber": 1, + "hasPreviousPage": false, + "hasNextPage": false, + "totalPages": 1, + "items": [ + { + "offeringId": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "primaryCode": { + "codeType": "identifier", + "code": "1234qwe12" + }, + "offeringType": "component", + "academicSession": "cafecafe-cafe-cafe-cafe-cafecafecafe", + "name": [ + { + "language": "en-GB", + "value": "some-program-offering" + } + ], + "abbreviation": "Test-INFOMQNM-20FS", + "description": [ + { + "language": "en-GB", + "value": ".." + } + ], + "teachingLanguage": "nld", + "modeOfDelivery": [ + "situated" + ], + "maxNumberStudents": 200, + "enrolledNumberStudents": 150, + "pendingNumberStudents": 50, + "minNumberStudents": 15, + "resultExpected": true, + "resultValueType": "1-10", + "link": "https://osiris.uu.nl/osiris_student_uuprd/OnderwijsCatalogusZoekCursus.do#submitForm?cursuscode=INFOMQNM", + "otherCodes": [ + { + "codeType": "identifier", + "code": "1234qwe12" + } + ], + "consumers": [ + { + "consumerKey": "rio", + "explanationRequiredPermission": "Toestemming is vereist omdat we daarom vragen.", + "requiredPermissionRegistration": "yes", + "registrationStatus": "open" + } + ], + "ext": {}, + "startDate": "2019-08-21", + "endDate": "2023-06-15", + "enrollStartDate": "2019-05-01", + "enrollEndDate": "2019-08-01", + "flexibleEntryPeriodStart": "2019-08-24", + "flexibleEntryPeriodEnd": "2019-09-24", + "addresses": [ + { + "addressType": "postal", + "street": "Moreelsepark", + "streetNumber": "48", + "additional": [ + { + "language": "en-GB", + "value": "On the other side of the road" + } + ], + "postalCode": "3511 EP", + "city": "Utrecht", + "countryCode": "NL", + "geolocation": { + "latitude": 52.089123, + "longitude": 5.113337 + }, + "ext": {} + } + ], + "priceInformation": [ + { + "costType": "total costs", + "amount": "340.84", + "vatAmount": "40", + "amountWithoutVat": "300.84", + "currency": "EUR", + "displayAmount": [ + { + "language": "nl-NL", + "value": "€380,84" + }, + { + "language": "en-US", + "value": "$401.17" + } + ], + "ext": {} + } + ], + "program": "{{programs/some}}" + } + ], + "ext": {} +} diff --git a/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj b/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj index da7c91b7..a9139821 100644 --- a/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj +++ b/test/nl/surf/eduhub_rio_mapper/e2e_helper.clj @@ -10,30 +10,35 @@ [nl.surf.eduhub-rio-mapper.cli :as cli] [nl.surf.eduhub-rio-mapper.clients-info :as clients-info] [nl.surf.eduhub-rio-mapper.http-utils :as http-utils] + [nl.surf.eduhub-rio-mapper.ooapi :as ooapi] [nl.surf.eduhub-rio-mapper.remote-entities-helper :as remote-entities] [nl.surf.eduhub-rio-mapper.rio :as rio] [nl.surf.eduhub-rio-mapper.rio.loader :as rio-loader] [nl.surf.eduhub-rio-mapper.xml-utils :as xml-utils]) (:import (java.util Base64) - (java.io ByteArrayInputStream))) + (java.io ByteArrayInputStream StringWriter) + (javax.xml.xpath XPathFactory))) (def ^:private last-seen-testing-contexts (atom nil)) (defn- print-testing-contexts "Print eye catching testing context (when it's not already printed)." [] - (when-not (= @last-seen-testing-contexts test/*testing-contexts*) - (reset! last-seen-testing-contexts test/*testing-contexts*) - (println) - (println "╔═══════════════════") - (println (str/replace (first test/*testing-contexts*) #"(?m)^\s*" "║ ")))) + (when (seq test/*testing-contexts*) + (when-not (= @last-seen-testing-contexts test/*testing-contexts*) + (reset! last-seen-testing-contexts test/*testing-contexts*) + (println) + (println "╔═══════════════════") + (println (str/replace (first test/*testing-contexts*) #"(?m)^\s*" "║ "))))) (def ^:private last-boxed-print (atom nil)) (defmacro print-boxed "Print pretty box around output of evaluating `form`." [title & form] - `(let [s# (with-out-str (do ~@form))] + `(let [sw# (StringWriter.) + r# (binding [*out* sw#] ~@form) + s# (str sw#)] (if (= @last-boxed-print s#) (do (print ".") @@ -44,7 +49,8 @@ (print "╭─────" ~title "\n│ ") (println (str/replace (str/trim s#) #"\n" "\n│ ")) (println "╰─────") - (reset! last-boxed-print s#))))) + (reset! last-boxed-print s#))) + r#)) (defn- print-soap-body "Print the body of a SOAP request or response." @@ -197,10 +203,11 @@ (let [[rio-id type] args] (str "/job/unlink/" rio-id "/" (name type))))) -(defn ooapi +(defn ooapi-id "Get OOAPI UUID of automatically uploaded fixture." - [name] - (get remote-entities/*session* name)) + [type id] + (let [name (str (name type) "/" id)] + (get remote-entities/*session* name))) (defn- interpret-post-job-args "Automatically find OOAPI ID from session. @@ -213,7 +220,7 @@ (let [[type id] (take-last 2 args)] (concat (drop-last args) [(if (and (keyword? type) (string? id)) - (let [uuid (ooapi (str (name type) "/" id))] + (let [uuid (ooapi-id type id)] (assert uuid (str "Expect a UUID for " id)) uuid) id)]))) @@ -310,9 +317,21 @@ (defmethod test/assert-expr 'job-result-opleidingseenheidcode [msg form] `(let [job# ~(second form) attrs# (job-result-attributes job#)] - (test/do-report {:type (if (job-result-opleidingseenheidcode job#) :pass :fail) - :message (or ~msg "Expect job result attributes to include opleidingseenheidcode."), - :expected '~form, :actual attrs#}))) + (test/do-report {:type (if (job-result-opleidingseenheidcode job#) :pass :fail) + :message (or ~msg "Expect job result attributes to include opleidingseenheidcode."), + :expected '~form, :actual attrs#}))) + +(defn job-result-aangebodenopleidingcode + "Short cut to `post-job` job response attributes aangebodenopleidingcode." + [job] + (job-result-attributes job :aangebodenopleidingcode)) + +(defmethod test/assert-expr 'job-result-aangebodenopleidingcode [msg form] + `(let [job# ~(second form) + attrs# (job-result-attributes job#)] + (test/do-report {:type (if (job-result-aangebodenopleidingcode job#) :pass :fail) + :message (or ~msg "Expect job result attributes to include aangebodenopleidingcode."), + :expected '~form, :actual attrs#}))) (defn job-has-diffs? "Returns `true` if \"diff\" is detected in given attributes." @@ -396,19 +415,22 @@ (def ^:private client-info (delay (clients-info/client-info (:clients @config) (:client-id env)))) -(defn rio-relations - "Call RIO `opleidingsrelaties-bij-opleidingseenheid`." - [code] - {:pre [(spec/valid? ::rio/opleidingscode code)]} +(defn- rio-get [req] (let [messages-atom (atom []) - result - (binding [http-utils/*http-messages* messages-atom] - (@rio-getter {::rio/type rio-loader/opleidingsrelaties-bij-opleidingseenheid - ::rio/opleidingscode code - :institution-oin (:institution-oin @client-info)}))] + result (binding [http-utils/*http-messages* messages-atom] + (@rio-getter req))] (print-http-messages @messages-atom) result)) +(defn rio-relations + "Call RIO `opvragen_opleidingsrelatiesBijOpleidingseenheid`." + [code] + {:pre [(spec/valid? ::rio/opleidingscode code)]} + (print-boxed "rio-relations" + (rio-get {::rio/type rio-loader/opleidingsrelaties-bij-opleidingseenheid + ::rio/opleidingscode code + :institution-oin (:institution-oin @client-info)}))) + (defn rio-with-relation? "Fetch relations of `rio-child` and test if it includes `rio-parent`. @@ -429,6 +451,39 @@ (recur (dec tries))) result))))) +(defn rio-opleidingseenheid + "Call RIO `opvragen_opleidingseenheid`." + [code] + {:pre [(spec/valid? ::rio/opleidingscode code)]} + (print-boxed "rio-opleidingseenheid" + (-> {::rio/type rio-loader/opleidingseenheid + ::rio/opleidingscode code + :institution-oin (:institution-oin @client-info) + :response-type :literal} + (rio-get)))) + +(defn rio-aangebodenopleiding + "Call RIO `opvragen_aangebodenOpleiding`." + [id] + {:pre [(spec/valid? ::ooapi/id id)]} + (print-boxed "rio-aangebodenopleiding" + (-> {::rio/type rio-loader/aangeboden-opleiding + ::ooapi/id id + :institution-oin (:institution-oin @client-info) + :response-type :literal} + (rio-get)))) + +(defn get-in-xml + "Get text node from `path` starting at `node`." + [node path] + (let [xpath (str "//" + (->> path + (map #(str "*[local-name()='" % "']")) + (str/join "/")))] + (.evaluate (.newXPath (XPathFactory/newInstance)) + xpath + node))) + ;; Using atoms to keep process to make interactive development easier. diff --git a/test/nl/surf/eduhub_rio_mapper/e2e_test.clj b/test/nl/surf/eduhub_rio_mapper/e2e_test.clj index efecdd2a..2629e0c7 100644 --- a/test/nl/surf/eduhub_rio_mapper/e2e_test.clj +++ b/test/nl/surf/eduhub_rio_mapper/e2e_test.clj @@ -1,114 +1,189 @@ (ns nl.surf.eduhub-rio-mapper.e2e-test (:require [clojure.test :refer :all] + [nl.jomco.http-status-codes :as http-status] [nl.surf.eduhub-rio-mapper.e2e-helper :refer :all] [nl.surf.eduhub-rio-mapper.remote-entities-helper :refer [remote-entities-fixture]]) (:import (java.util UUID))) (use-fixtures :once with-running-mapper remote-entities-fixture) -(def test-eigensleutel (UUID/randomUUID)) +(deftest ^:e2e create-edspecs-and-program + (testing "create edspecs" + (testing "scenario [1a]: Test /job/dry-run to see the difference between the edspec parent in OOAPI en de opleidingeenheid in RIO. You can expect RIO to be empty, when you start fresh." + (let [job (post-job :dry-run/upsert :education-specifications "parent-program")] + (is (job-done? job)) + (is (job-dry-run-not-found? job)))) + + (testing "scenario [1b]: Test /job/upsert with the program. You can expect 'done' and a opleidingeenheid in RIO is inserted." + (let [parent-job (post-job :upsert :education-specifications "parent-program")] + (is (job-done? parent-job)) + (is (job-result-opleidingseenheidcode parent-job)) + (let [xml (rio-opleidingseenheid (job-result-opleidingseenheidcode parent-job))] + (is (= "parent-program education specification" + (get-in-xml xml ["hoOpleiding" "hoOpleidingPeriode" "naamLang"])))) + + (testing "(you can repeat this to test an update of the same data.)" + (let [job (post-job :upsert :education-specifications "parent-program")] + (is (job-done? job)))) + + (testing "scenario [1a]: Test /job/dry-run to see the difference between the edspec parent in OOAPI en de opleidingeenheid in RIO. You can expect them to be the same." + (let [job (post-job :dry-run/upsert :education-specifications "parent-program")] + (is (job-done? job)) + (is (job-dry-run-found? job)) + (is (job-without-diffs? job)))) + + (testing "scenario [1c]: Test /job/upsert with the edspec child. You can expect 'done' and a variant in RIO is inserted met een relatie met de parent." + (let [child-job (post-job :upsert :education-specifications "child-program")] + (is (job-done? child-job)) + (is (rio-with-relation? (job-result-opleidingseenheidcode parent-job) + (job-result-opleidingseenheidcode child-job))) + (let [xml (rio-opleidingseenheid (job-result-opleidingseenheidcode child-job))] + (is (= "child-program education specification" + (get-in-xml xml ["hoOpleiding" "hoOpleidingPeriode" "naamLang"])))) + + (let [test-eigensleutel (UUID/randomUUID)] + (testing "scenario [2a]: Test /job/link of the edspec parent and create a new 'eigen sleutel'. You can expect the 'eigen sleutel' to be changed." + (let [job (post-job :link (job-result-opleidingseenheidcode parent-job) + :education-specifications test-eigensleutel)] + (is (job-done? job)) + (is (job-has-diffs? job))) + + (testing "(you can repeat this to expect an error becoause the new 'eigen sleutel' already exists.)" + (let [job (post-job :link (job-result-opleidingseenheidcode child-job) + :education-specifications test-eigensleutel)] + (is (job-error? job)))))))) + + (testing "scenario [2d]: Test /job/unlink to reset the edspec parent to an empty 'eigen sleutel'." + (let [job (post-job :unlink (job-result-opleidingseenheidcode parent-job) + :education-specifications)] + (is (job-done? job)))) + + (testing "scenario [2b]: Test /job/link to reset the edspec parent to the old 'eigen sleutel'." + (let [job (post-job :link (job-result-opleidingseenheidcode parent-job) + :education-specifications "parent-program")] + (is (job-done? job)) + (is (job-has-diffs? job))))))) + + (testing "create a program (for the edSpec child)" + (testing "scenario [4a]: Test /job/dry-run to see the difference between the program in OOAPI en de aangeboden opleiding in RIO. You can expect RIO to be empty, when you start fresh." + (let [job (post-job :dry-run/upsert :programs "some")] + (is (job-done? job)) + (is (job-dry-run-not-found? job)))) + + (testing "scenario [4c]: Test /job/delete with the program. You can expect an error, because the program is not upserted yet." + (let [job (post-job :delete :programs "some")] + (is (job-error? job)))) + + (testing "scenario [4b]: Test /job/upsert with the program. You can expect a new aangeboden opleiding. This aangeboden opleiding includes a periode and a cohort. (you can repeat this to test an update of the same data.)" + (let [job (post-job :upsert :programs "some")] + (is (job-done? job)) + (is (job-result-aangebodenopleidingcode job)) + (is (= (str (ooapi-id :programs "some")) + (job-result-aangebodenopleidingcode job)) + "aangebodenopleidingcode is the same as the OOAPI id") + (let [xml (rio-aangebodenopleiding (job-result-aangebodenopleidingcode job))] + (is (= "2008-10-18" + (get-in-xml xml ["aangebodenHOOpleiding" "aangebodenHOOpleidingPeriode" "begindatum"]))) + (is (= "1234qwe12" + (get-in-xml xml ["aangebodenHOOpleiding" "aangebodenHOOpleidingCohort" "cohortcode"])))))) + + (testing "scenario [4a]: Test /job/dry-run to see the difference between the program in OOAPI en de opleidingeenheid in RIO. You can expect them to be the same." + (let [job (post-job :dry-run/upsert :programs "some")] + (is (job-done? job)) + (is (job-dry-run-found? job)) + (is (job-without-diffs? job)))) + + (let [test-eigensleutel (UUID/randomUUID)] + (testing "scenario [5a]: Test /job/link of the program and create a new 'eigen sleutel'. You can expect the 'eigen sleutel' to be changed." + (let [job (post-job :link (str (ooapi-id :programs "some")) + :programs test-eigensleutel)] + (is (job-done? job)) + (is (job-has-diffs? job))) -(deftest ^:e2e create-edspecs - (testing "scenario [1a]: Test /job/dry-run to see the difference - between the edspec parent in OOAPI en de opleidingeenheid - in RIO. You can expect RIO to be empty, when you start - fresh." - (let [job (post-job :dry-run/upsert :education-specifications "parent")] - (is (job-done? job)) - (is (job-dry-run-not-found? job)))) + (testing "(you can repeat this to expect an error becoause the new 'eigen sleutel' already exists.)" + (let [job (post-job :link (str (ooapi-id :programs "some")) + :programs test-eigensleutel)] + (is (job-done? job)) + (is (job-without-diffs? job))))) - ;; TODO this fails because eduspec is added to rio but linking - ;; currently silently fails - (comment - (testing "scenario [4b]: Test /job/upsert with the program. You can - expect an error, because the edspec child is not - upserted." - (let [job (post-job :upsert :education-specifications "child")] - (is (job-error? job))))) - - (testing "scenario [1b]: Test /job/upsert with the edspec - parent. You can expect 'done' and a opleidingeenheid in - RIO is inserted." - (let [parent-job (post-job :upsert :education-specifications "parent")] - (is (job-done? parent-job)) - (is (job-result-opleidingseenheidcode parent-job)) - - (testing "(you can repeat this to test an update of the same data.)" - (let [job (post-job :upsert :education-specifications "parent")] + (testing "scenario [5d]: Test /job/unlink to reset the program to an empty 'eigen sleutel'." + (let [job (post-job :unlink (str (ooapi-id :programs "some")) + :programs test-eigensleutel)] (is (job-done? job)))) - (testing "scenario [1a]: Test /job/dry-run to see the difference - between the edspec parent in OOAPI en de opleidingeenheid - in RIO. You can expect them to be the same." - (let [job (post-job :dry-run/upsert :education-specifications "parent")] - (is (job-done? job)) - (is (job-dry-run-found? job)) - (is (job-without-diffs? job)))) - - (testing "scenario [1c]: Test /job/upsert with the edspec child. You - can expect 'done' and a variant in RIO is inserted met een - relatie met de parent." - (let [child-job (post-job :upsert :education-specifications "child")] - (is (job-done? child-job)) - (is (rio-with-relation? (job-result-opleidingseenheidcode parent-job) - (job-result-opleidingseenheidcode child-job))) - - (testing "scenario [2a]: Test /job/link of the edspec parent and - create a new 'eigen sleutel'. You can expect the 'eigen - sleutel' to be changed." - (let [job (post-job :link (job-result-opleidingseenheidcode parent-job) - :education-specifications test-eigensleutel)] - (is (job-done? job)) - (is (job-has-diffs? job))) - - (testing "(you can repeat this to expect an error becoause the new - 'eigen sleutel' already exists.)" - (let [job (post-job :link (job-result-opleidingseenheidcode child-job) - :education-specifications test-eigensleutel)] - (is (job-error? job))))))) - - (testing "scenario [2d]: Test /job/unlink to reset the edspec parent - to an empty 'eigen sleutel'." - (let [job (post-job :unlink (job-result-opleidingseenheidcode parent-job) - :education-specifications)] - (is (job-done? job)))) - - (testing "scenario [2b]: Test /job/link to reset the edspec parent - to the old 'eigen sleutel'." - (let [job (post-job :link (job-result-opleidingseenheidcode parent-job) - :education-specifications "parent")] + (testing "scenario [5b]: Test /job/link to reset the program to the old 'eigen sleutel'." + (let [job (post-job :link (str (ooapi-id :programs "some")) + :programs (ooapi-id :programs "some"))] (is (job-done? job)) (is (job-has-diffs? job))))))) -(deftest ^:e2e try-to-create-edspecs-with-invalid-data - ;; scenario [3a]: Test /job/upsert/ to see how the rio mapper reacts on an invalid api call. You can expect a 404 response. - ;; scenario [3b]: Test /job/upsert with an edspec parent with an invalid type attribute. You can expect 'error'. - :TODO) - -(deftest ^:e2e create-a-program-for-the-edspec-child - ;; scenario [4a]: Test /job/dry-run to see the difference between the program in OOAPI en de aangeboden opleiding in RIO. You can expect RIO to be empty, when you start fresh. - ;; scenario [4c]: Test /job/delete with the program. You can expect an error, because the program is not upserted yet. - ;; scenario [4b]: Test /job/upsert with the program. You can expect a new aangeboden opleiding. This aangeboden opleiding includes a periode and a cohort. (you can repeat this to test an update of the same data.) - ;; scenario [4a]: Test /job/dry-run to see the difference between the program in OOAPI en de opleidingeenheid in RIO. You can expect them to be the same. - ;; scenario [5a]: Test /job/link of the program and create a new 'eigen sleutel'. You can expect the 'eigen sleutel' to be changed. (you can repeat this to expect an error becoause the new 'eigen sleutel' already exists.) - ;; scenario [5d]: Test /job/unlink to reset the program to an empty 'eigen sleutel'. - ;; scenario [5b]: Test /job/link to reset the program to the old 'eigen sleutel'. - :TODO) - (deftest ^:e2e try-to-create-a-program-with-invalid-data - ;; scenario [6a]: Test /job/upsert with a program with an invalid onderwijsaanbieder attribute. You can expect 'error'. - ;; scenario [6b]: Test /job/upsert with a program with an invalid onderwijslocatie attribute. You can expect 'error'. - :TODO) + (testing "scenario [6a]: Test /job/upsert with a program with an invalid onderwijsaanbieder attribute. You can expect 'error'." + (let [job (post-job :upsert :programs "bad-edu-offerer")] + (is (job-error? job)))) + (testing "scenario [6b]: Test /job/upsert with a program with an invalid onderwijslocatie attribute. You can expect 'error'." + (let [job (post-job :upsert :programs "bad-edu-location")] + (is (job-error? job))))) (deftest ^:e2e create-a-course-with-its-own-edspec - ;; scenario [7a]: Test /job/upsert with the edspec for a course. You can expect 'done'. - ;; scenario [7c]: Test /job/dry-run to see the difference between the course in OOAPI en de aangeboden opleiding in RIO. You can expect RIO to be empty, when you start fresh. - ;; scenario [7e]: Test /job/delete with the course. You can expect an error, because the course is not upserted yet. - ;; scenario [7d]: Test /job/upsert with the course. You can expect a new aangeboden opleiding. This aangeboden opleiding includes a periode and a cohort. (you can repeat this to test an update of the same data.) - ;; scenario [7c]: Test /job/dry-run to see the difference between the course in OOAPI en de aangeboden opleiding in RIO. You can expect them to be the same. - ;; scenario [8a]: Test /job/link of the course and create a new 'eigen sleutel'. You can expect the 'eigen sleutel' to be changed. (you can repeat this to expect an error becoause the new 'eigen sleutel' already exists.) - ;; scenario [8d]: Test /job/unlink to reset the course to an empty 'eigen sleutel'. - ;; scenario [8b]: Test /job/link to reset the course to the old 'eigen sleutel'. - :TODO) - -;; TODO add test to upsert non existing eduspec + (testing "scenario [7a]: Test /job/upsert with the edspec for a course. You can expect 'done'." + (let [job (post-job :upsert :education-specifications "parent-course")] + (is (job-done? job)))) + + (testing "scenario [7c]: Test /job/dry-run to see the difference between the course in OOAPI en de aangeboden opleiding in RIO. You can expect RIO to be empty, when you start fresh." + (let [job (post-job :dry-run/upsert :courses "some")] + (is (job-done? job)) + (is (job-dry-run-not-found? job)))) + + (testing "scenario [7e]: Test /job/delete with the course. You can expect an error, because the course is not upserted yet." + (let [job (post-job :delete :courses "some")] + (is (job-error? job)))) + + (testing "scenario [7d]: Test /job/upsert with the course. You can expect a new aangeboden opleiding. This aangeboden opleiding includes a periode and a cohort. (you can repeat this to test an update of the same data.)" + (let [job (post-job :upsert :courses "some")] + (is (job-done? job)) + (let [xml (rio-aangebodenopleiding (job-result-aangebodenopleidingcode job))] + (is (= "1994-09-05" + (get-in-xml xml ["aangebodenHOOpleidingsonderdeel" "eersteInstroomDatum"]))) + (is (= "2050-11-10" + (get-in-xml xml ["aangebodenHOOpleidingsonderdeel" "einddatum"])))))) + + (testing "scenario [7c]: Test /job/dry-run to see the difference between the course in OOAPI en de aangeboden opleiding in RIO. You can expect them to be the same." + (let [job (post-job :dry-run/upsert :courses "some")] + (is (job-done? job)) + (is (job-dry-run-found? job)) + (is (job-without-diffs? job)))) + + (let [test-eigensleutel (UUID/randomUUID)] + (testing "scenario [8a]: Test /job/link of the course and create a new 'eigen sleutel'. You can expect the 'eigen sleutel' to be changed." + (let [job (post-job :link (str (ooapi-id :courses "some")) + :courses test-eigensleutel)] + (is (job-done? job)) + (is (job-has-diffs? job)))) + + (testing "(you can repeat this to expect an error becoause the new 'eigen sleutel' already exists.)" + (let [job (post-job :link (str (ooapi-id :courses "some")) + :courses test-eigensleutel)] + (is (job-done? job)) + (is (job-without-diffs? job)))) + + (testing "scenario [8d]: Test /job/unlink to reset the course to an empty 'eigen sleutel'." + (let [job (post-job :unlink (str (ooapi-id :courses "some")) + :courses test-eigensleutel)] + (is (job-done? job))))) + + (testing "scenario [8b]: Test /job/link to reset the course to the old 'eigen sleutel'." + (let [job (post-job :link (str (ooapi-id :courses "some")) + :courses (ooapi-id :courses "some"))] + (is (job-done? job)) + (is (job-has-diffs? job))))) + +(deftest ^:e2e try-to-create-edspecs-with-invalid-data + (testing "scenario [3a]: Test /job/upsert/ to see how the rio mapper reacts on an invalid api call. You can expect a 404 response." + (let [job (post-job :upsert "not-a-valid-type" (UUID/randomUUID))] + (is (= http-status/not-found (:status job))))) + + (testing "scenario [3b]: Test /job/upsert with an edspec parent with an invalid type attribute. You can expect 'error'." + (let [job (post-job :upsert :education-specifications "bad-type")] + (is (job-error? job)) + (is (= "fetching-ooapi" (job-result job :phase)))))) diff --git a/test/nl/surf/eduhub_rio_mapper/remote_entities_helper.clj b/test/nl/surf/eduhub_rio_mapper/remote_entities_helper.clj index c73a3545..10e27bef 100644 --- a/test/nl/surf/eduhub_rio_mapper/remote_entities_helper.clj +++ b/test/nl/surf/eduhub_rio_mapper/remote_entities_helper.clj @@ -7,19 +7,19 @@ the JSON files are replaced by random UUIDs and the paths and base names replace with those UUIDs when referenced using `{{` and `}}`. - For instance `education-specifications/parent.json` may contain: + For instance `education-specifications/some.json` may contain: ``` { \"level\": \"bachelor\", - \"parent\": \"{{education-specifications/parent}}\", + \"parent\": \"{{education-specifications/some}}\", \"organization\": \"{{organizations/acme}}\", ``` Say we generated a UUID of `beefbeef-beef-beef-beef-beefbeefbeef` for this entity and `cafecafe-cafe-cafe-cafe-cafecafecafe` for `organizations/acme`, the uploaded version will be named - `education-specifications/beefbeef-beef-beef-beef-beefbeefbeef.json` + `education-specifications/beefbeef-beef-beef-beef-beefbeefbeef` and contain: ``` @@ -30,7 +30,12 @@ ``` and the uploaded version of `organizations/acme` will be named - `organizations/cafecafe-cafe-cafe-cafe-cafecafecafe.json`. + `organizations/cafecafe-cafe-cafe-cafe-cafecafecafe`. + + Nested resources are handled slightly different. For resource like + `programs/some/offerings.json` the `programs/some` part will be + considered its name and the uploaded object will be named something + like: `programs/cafecafe-cafe-cafe-cafe-cafecafecafe/offerings`. This only works if the following environment variables are set: @@ -154,20 +159,28 @@ io/resource io/file)] (when-not (and dir (.isDirectory dir)) - (throw (IllegalStateException. (str entities-resource-path " is not a directory -- are you running in a jar?")))) + (throw (IllegalStateException. + (str entities-resource-path " is not a directory -- are you running in a jar?")))) dir)) -(defn- input-resources - [] - (->> (entities-dir) - (.listFiles) - (filter #(.isDirectory %)) - (remove #(string/starts-with? (.getName %) ".")) - (mapcat (fn [dir] - (let [dirname (.getName dir)] - (->> (.listFiles dir) - (map #(str dirname "/" (.getName %))) - (filter #(string/ends-with? % ".json")))))))) +(defn- input-files + ([] + (input-files (entities-dir))) + ([dir] + (mapcat #(if (.isDirectory %) (input-files %) [%]) (.listFiles dir)))) + +(defn- file->base + [file] + (string/replace + (subs (.getCanonicalPath file) + (inc (count (.getCanonicalPath (entities-dir))))) + #"\.json$" "")) + +(defn- file->key + [file] + (second + (re-matches #"^([^/]+/[^./]+).*" + (file->base file)))) (defn make-session "Return a new session map of entity names to random ids. @@ -177,18 +190,21 @@ So `courses/some-course.json` will be mapped as: - \"courses/some-course\" => some-random-uuid. + \"courses/some-course\" => some-random-uuid + + and `programs/some-program/offerings.json` will be mapped as: + + \"programs/some-program\" => some-other-random-uuid See also `with-session`" [] - (let [input (input-resources)] - (into {} (map #(vector (string/replace % #".json" "") (UUID/randomUUID)) input)))) - -(defn- resource-path - [n] - (str entities-resource-path "/" n ".json")) + (into {} + (->> (input-files) + (map file->key) + (set) + (map #(vector % (UUID/randomUUID)))))) -(defn- replace-exprs +(defn- replace-content-exprs [templ session] (string/replace templ #"\{\{\s*([^}\s]+)\s*}\}" @@ -197,13 +213,41 @@ (throw (ex-info (str "Can't find name '" name "'") {:name name :session session}))))))) -(defn- resource-content - [n session] - (-> n - resource-path - io/resource - slurp - (replace-exprs session))) +(defn- object-content + [f session] + (-> f + (slurp) + (replace-content-exprs session))) + +(defn- object-location + "Determine object location from file name and session." + [f session] + (let [loc (file->base f)] + (loop [[[k v] & more] session] + (assert k) + (if (string/starts-with? loc k) + (let [[_ base] (re-matches #"^([^/]+)/.*" k)] + (str base "/" (string/replace loc k (str v)))) + (recur more))))) + +(defn- remote-objects + [session] + (map (fn [f] + {:path (object-location f session) + :body (object-content f session)}) + (input-files))) + +(defn- put-session + [info container-name session] + (println "Adding remote entities to" container-name) + (doseq [object (remote-objects session)] + (os-put-object info container-name object))) + +(defn- delete-session + [info container-name session] + (println "Removing entities from" container-name) + (doseq [object (remote-objects session)] + (os-delete-object info container-name object))) (def ^:dynamic *session* @@ -227,26 +271,6 @@ :post [%]} (get *session* n)) - -(defn- remote-objects - [session] - (->> session - (map (fn [[n id]] - {:path (str (string/replace n #"/.*" "") "/" id) - :body (resource-content n session)})))) - -(defn- put-session - [info container-name session] - (println "Adding remote entities to" container-name) - (doseq [object (remote-objects session)] - (os-put-object info container-name object))) - -(defn- delete-session - [info container-name session] - (println "Removing entities from" container-name) - (doseq [object (remote-objects session)] - (os-delete-object info container-name object))) - (defn remote-entities-fixture "A fixture that uploads the entities to the remote container.