{ "info": { "_postman_id": "c9e5e08f-e878-4d8a-b24c-5f866eb4c735", "name": "Dynamic Question Type Builder - Runtime Complete", "description": "Complete collection for the new dynamic question type builder flow: component catalog, definition schema CRUD, dynamic payload validation, and DYNAMIC question lifecycle.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{access_token}}", "type": "string" } ] }, "variable": [ { "key": "base_url", "value": "http://localhost:8080" }, { "key": "access_token", "value": "" }, { "key": "definition_key", "value": "dynamic_builder_{{$timestamp}}" }, { "key": "dynamic_definition_id", "value": "" }, { "key": "dynamic_question_id", "value": "" } ], "item": [ { "name": "01 - Builder Catalog and Validation", "item": [ { "name": "Get Component Catalog", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/component-catalog", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "component-catalog" ] } }, "response": [] }, { "name": "Validate Definition (Valid)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});", "const body = pm.response.json();", "pm.test(\"valid = true\", function () {", " pm.expect(body.data.valid).to.eql(true);", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"stimulus_component_kinds\": [\"QUESTION_TEXT\", \"IMAGE\", \"TABLE\"],\n \"response_component_kinds\": [\"OPTION\", \"ANSWER_TIMER\"]\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions/validate-question-type-definition", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "validate-question-type-definition" ] } }, "response": [] }, { "name": "Validate Definition (Invalid - Timer Only)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 400\", function () {", " pm.response.to.have.status(400);", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"stimulus_component_kinds\": [\"QUESTION_TEXT\"],\n \"response_component_kinds\": [\"ANSWER_TIMER\"]\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions/validate-question-type-definition", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "validate-question-type-definition" ] } }, "response": [] } ] }, { "name": "02 - Definition CRUD (Schema-Driven)", "item": [ { "name": "Create Dynamic Definition (with schema)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", "});", "const body = pm.response.json();", "pm.collectionVariables.set(\"dynamic_definition_id\", body.data.id);" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"key\": \"{{definition_key}}\",\n \"display_name\": \"Dynamic Grammar + Visual MCQ\",\n \"description\": \"Reusable dynamic question type with question text, image, table, and option responses.\",\n \"stimulus_component_kinds\": [\"QUESTION_TEXT\", \"IMAGE\", \"TABLE\"],\n \"response_component_kinds\": [\"OPTION\", \"ANSWER_TIMER\"],\n \"stimulus_schema\": [\n {\n \"id\": \"prompt\",\n \"kind\": \"QUESTION_TEXT\",\n \"label\": \"Prompt\",\n \"required\": true,\n \"config\": {\n \"max_length\": 1000\n }\n },\n {\n \"id\": \"illustration\",\n \"kind\": \"IMAGE\",\n \"label\": \"Supporting Image\",\n \"required\": false,\n \"config\": {\n \"allowed_formats\": [\"png\", \"jpg\", \"webp\"]\n }\n },\n {\n \"id\": \"data_table\",\n \"kind\": \"TABLE\",\n \"label\": \"Reference Table\",\n \"required\": false,\n \"config\": {\n \"max_rows\": 20,\n \"max_columns\": 8\n }\n }\n ],\n \"response_schema\": [\n {\n \"id\": \"choices\",\n \"kind\": \"OPTION\",\n \"label\": \"Answer Options\",\n \"required\": true,\n \"config\": {\n \"min_options\": 2,\n \"max_options\": 6,\n \"allow_multiple\": false\n }\n },\n {\n \"id\": \"timer\",\n \"kind\": \"ANSWER_TIMER\",\n \"label\": \"Answer Time Limit\",\n \"required\": false,\n \"config\": {\n \"min_seconds\": 5,\n \"max_seconds\": 180\n }\n }\n ],\n \"status\": \"ACTIVE\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions/type-definitions", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "type-definitions" ] } }, "response": [] }, { "name": "Get Definition By ID", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/type-definitions/{{dynamic_definition_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "type-definitions", "{{dynamic_definition_id}}" ] } }, "response": [] }, { "name": "List Definitions (include system)", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/type-definitions?include_system=true&status=ACTIVE", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "type-definitions" ], "query": [ { "key": "include_system", "value": "true" }, { "key": "status", "value": "ACTIVE" } ] } }, "response": [] }, { "name": "Update Definition (schema evolution)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});" ], "type": "text/javascript" } } ], "request": { "method": "PUT", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"display_name\": \"Dynamic Grammar + Visual MCQ (Updated)\",\n \"response_component_kinds\": [\"OPTION\", \"ANSWER_TIMER\", \"SEQUENCE_ORDER\"],\n \"response_schema\": [\n {\n \"id\": \"choices\",\n \"kind\": \"OPTION\",\n \"label\": \"Answer Options\",\n \"required\": true,\n \"config\": {\n \"min_options\": 2,\n \"max_options\": 6,\n \"allow_multiple\": false\n }\n },\n {\n \"id\": \"timer\",\n \"kind\": \"ANSWER_TIMER\",\n \"label\": \"Answer Time Limit\",\n \"required\": false,\n \"config\": {\n \"min_seconds\": 5,\n \"max_seconds\": 180\n }\n },\n {\n \"id\": \"ordering\",\n \"kind\": \"SEQUENCE_ORDER\",\n \"label\": \"Optional Sequence Order\",\n \"required\": false,\n \"config\": {\n \"max_items\": 8\n }\n }\n ]\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions/type-definitions/{{dynamic_definition_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "type-definitions", "{{dynamic_definition_id}}" ] } }, "response": [] }, { "name": "Update Definition (Invalid Schema Kind) - Expect 400", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 400\", function () {", " pm.response.to.have.status(400);", "});" ], "type": "text/javascript" } } ], "request": { "method": "PUT", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"response_schema\": [\n {\n \"id\": \"bad\",\n \"kind\": \"NON_EXISTENT_KIND\",\n \"required\": true\n }\n ]\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions/type-definitions/{{dynamic_definition_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "type-definitions", "{{dynamic_definition_id}}" ] } }, "response": [] } ] }, { "name": "03 - Dynamic Question Runtime", "item": [ { "name": "Create Dynamic Question (valid payload)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", "});", "const body = pm.response.json();", "pm.collectionVariables.set(\"dynamic_question_id\", body.data.id);", "pm.test(\"Question type is DYNAMIC\", function () {", " pm.expect(body.data.question_type).to.eql(\"DYNAMIC\");", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"question_text\": \"Complete the sentence using the table and image.\",\n \"question_type\": \"DYNAMIC\",\n \"question_type_definition_id\": {{dynamic_definition_id}},\n \"difficulty_level\": \"MEDIUM\",\n \"points\": 2,\n \"status\": \"DRAFT\",\n \"dynamic_payload\": {\n \"stimulus\": [\n {\n \"id\": \"prompt\",\n \"kind\": \"QUESTION_TEXT\",\n \"value\": \"Select the best completion based on the visual and table.\"\n },\n {\n \"id\": \"illustration\",\n \"kind\": \"IMAGE\",\n \"value\": \"https://cdn.example.com/images/sample-grammar-scene.jpg\"\n },\n {\n \"id\": \"data_table\",\n \"kind\": \"TABLE\",\n \"value\": {\n \"columns\": [\"Verb\", \"Past Form\"],\n \"rows\": [\n [\"go\", \"went\"],\n [\"write\", \"wrote\"]\n ]\n }\n }\n ],\n \"response\": [\n {\n \"id\": \"choices\",\n \"kind\": \"OPTION\",\n \"value\": {\n \"options\": [\n {\"id\": \"a\", \"text\": \"I goed to school\", \"is_correct\": false},\n {\"id\": \"b\", \"text\": \"I went to school\", \"is_correct\": true}\n ]\n }\n },\n {\n \"id\": \"timer\",\n \"kind\": \"ANSWER_TIMER\",\n \"value\": {\n \"seconds\": 30\n }\n }\n ]\n }\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions" ] } }, "response": [] }, { "name": "Get Dynamic Question By ID", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/{{dynamic_question_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "{{dynamic_question_id}}" ] } }, "response": [] }, { "name": "Update Dynamic Question (valid payload)", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 200\", function () {", " pm.response.to.have.status(200);", "});" ], "type": "text/javascript" } } ], "request": { "method": "PUT", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"question_text\": \"Updated: choose the best sentence.\",\n \"question_type_definition_id\": {{dynamic_definition_id}},\n \"question_type\": \"DYNAMIC\",\n \"status\": \"PUBLISHED\",\n \"dynamic_payload\": {\n \"stimulus\": [\n {\n \"id\": \"prompt\",\n \"kind\": \"QUESTION_TEXT\",\n \"value\": \"Pick the grammatically correct option.\"\n }\n ],\n \"response\": [\n {\n \"id\": \"choices\",\n \"kind\": \"OPTION\",\n \"value\": {\n \"options\": [\n {\"id\": \"a\", \"text\": \"He go home yesterday\", \"is_correct\": false},\n {\"id\": \"b\", \"text\": \"He went home yesterday\", \"is_correct\": true}\n ]\n }\n }\n ]\n }\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions/{{dynamic_question_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "{{dynamic_question_id}}" ] } }, "response": [] }, { "name": "List Questions (question_type=DYNAMIC)", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions?question_type=DYNAMIC&limit=20&offset=0", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions" ], "query": [ { "key": "question_type", "value": "DYNAMIC" }, { "key": "limit", "value": "20" }, { "key": "offset", "value": "0" } ] } }, "response": [] }, { "name": "Search Questions", "request": { "method": "GET", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/search?q=choose&limit=20&offset=0", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "search" ], "query": [ { "key": "q", "value": "choose" }, { "key": "limit", "value": "20" }, { "key": "offset", "value": "0" } ] } }, "response": [] } ] }, { "name": "04 - Negative Runtime Validation Cases", "item": [ { "name": "Create Dynamic Question without dynamic_payload - Expect 400", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 400\", function () {", " pm.response.to.have.status(400);", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"question_text\": \"Should fail\",\n \"question_type\": \"DYNAMIC\",\n \"question_type_definition_id\": {{dynamic_definition_id}},\n \"difficulty_level\": \"EASY\"\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions" ] } }, "response": [] }, { "name": "Create Dynamic Question with disallowed element kind - Expect 400", "event": [ { "listen": "test", "script": { "exec": [ "pm.test(\"Status code is 400\", function () {", " pm.response.to.have.status(400);", "});" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n \"question_text\": \"Should fail because AUDIO_CLIP is not allowed by selected definition\",\n \"question_type\": \"DYNAMIC\",\n \"question_type_definition_id\": {{dynamic_definition_id}},\n \"dynamic_payload\": {\n \"stimulus\": [\n {\n \"id\": \"audio1\",\n \"kind\": \"AUDIO_CLIP\",\n \"value\": \"https://cdn.example.com/audio/not-allowed.mp3\"\n }\n ],\n \"response\": [\n {\n \"id\": \"choices\",\n \"kind\": \"OPTION\",\n \"value\": {\n \"options\": [\n {\"id\": \"a\", \"text\": \"A\", \"is_correct\": true},\n {\"id\": \"b\", \"text\": \"B\", \"is_correct\": false}\n ]\n }\n }\n ]\n }\n}" }, "url": { "raw": "{{base_url}}/api/v1/questions", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions" ] } }, "response": [] } ] }, { "name": "05 - Cleanup", "item": [ { "name": "Delete Dynamic Question", "request": { "method": "DELETE", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/{{dynamic_question_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "{{dynamic_question_id}}" ] } }, "response": [] }, { "name": "Delete Dynamic Definition", "request": { "method": "DELETE", "header": [], "url": { "raw": "{{base_url}}/api/v1/questions/type-definitions/{{dynamic_definition_id}}", "host": [ "{{base_url}}" ], "path": [ "api", "v1", "questions", "type-definitions", "{{dynamic_definition_id}}" ] } }, "response": [] } ] } ] }