feat: add auto-create collection option to mock server creation (#5637)
Co-authored-by: Anwarul Islam <anwaarulislaam@gmail.com>
This commit is contained in:
parent
cd82eb212d
commit
008335c715
18 changed files with 1342 additions and 143 deletions
|
|
@ -892,6 +892,13 @@ export const MOCK_SERVER_NOT_FOUND = 'mock_server/not_found';
|
||||||
*/
|
*/
|
||||||
export const MOCK_SERVER_INVALID_COLLECTION = 'mock_server/invalid_collection';
|
export const MOCK_SERVER_INVALID_COLLECTION = 'mock_server/invalid_collection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock server collection creation failed
|
||||||
|
* (MockServerService)
|
||||||
|
*/
|
||||||
|
export const MOCK_SERVER_COLLECTION_CREATION_FAILED =
|
||||||
|
'mock_server/collection_creation_failed';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock server already exists for this collection
|
* Mock server already exists for this collection
|
||||||
* (MockServerService)
|
* (MockServerService)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,781 @@
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
const generateRefId = () => `${Date.now().toString(36)}_${randomUUID()}`;
|
||||||
|
|
||||||
|
export const mockServerCollRequestExample = (
|
||||||
|
collectionName: string = 'Hoppscotch API Mock example',
|
||||||
|
) => {
|
||||||
|
const baseEnv = '<<mockUrl>>';
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
v: 10,
|
||||||
|
name: collectionName,
|
||||||
|
folders: [],
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
v: '16',
|
||||||
|
_ref_id: `req_${generateRefId()}`,
|
||||||
|
name: 'addPet',
|
||||||
|
method: 'POST',
|
||||||
|
endpoint: baseEnv + '/v2/pet',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '{\n\t"id": 1,\n\t"category": {\n\t\t"id": 1,\n\t\t"name": "string"\n\t},\n\t"name": "doggie",\n\t"photoUrls": [\n\t\t"string"\n\t],\n\t"tags": [],\n\t"status": "available"\n}',
|
||||||
|
},
|
||||||
|
preRequestScript: '',
|
||||||
|
testScript: '',
|
||||||
|
requestVariables: [],
|
||||||
|
responses: {
|
||||||
|
'Invalid input': {
|
||||||
|
name: 'Invalid input',
|
||||||
|
status: 'Method Not Allowed',
|
||||||
|
code: 405,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'addPet',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '{\n\t"id": 1,\n\t"category": {\n\t\t"id": 1,\n\t\t"name": "string"\n\t},\n\t"name": "doggie",\n\t"photoUrls": [\n\t\t"string"\n\t],\n\t"tags": [],\n\t"status": "available"\n}',
|
||||||
|
},
|
||||||
|
endpoint: baseEnv + '/v2/pet',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'POST',
|
||||||
|
requestVariables: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: '16',
|
||||||
|
_ref_id: `req_${generateRefId()}`,
|
||||||
|
name: 'updatePet',
|
||||||
|
method: 'PUT',
|
||||||
|
endpoint: baseEnv + '/v2/pet',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '{\n\t"id": 1,\n\t"category": {\n\t\t"id": 1,\n\t\t"name": "string"\n\t},\n\t"name": "doggie",\n\t"photoUrls": [\n\t\t"string"\n\t],\n\t"tags": [],\n\t"status": "available"\n}',
|
||||||
|
},
|
||||||
|
preRequestScript: '',
|
||||||
|
testScript: '',
|
||||||
|
requestVariables: [],
|
||||||
|
responses: {
|
||||||
|
'Invalid ID supplied': {
|
||||||
|
name: 'Invalid ID supplied',
|
||||||
|
status: 'Bad Request',
|
||||||
|
code: 400,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'updatePet',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '{\n\t"id": 1,\n\t"category": {\n\t\t"id": 1,\n\t\t"name": "string"\n\t},\n\t"name": "doggie",\n\t"photoUrls": [\n\t\t"string"\n\t],\n\t"tags": [],\n\t"status": "available"\n}',
|
||||||
|
},
|
||||||
|
endpoint: baseEnv + '/v2/pet',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'PUT',
|
||||||
|
requestVariables: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Pet not found': {
|
||||||
|
name: 'Pet not found',
|
||||||
|
status: 'Not Found',
|
||||||
|
code: 404,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'updatePet',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '{\n\t"id": 1,\n\t"category": {\n\t\t"id": 1,\n\t\t"name": "string"\n\t},\n\t"name": "doggie",\n\t"photoUrls": [\n\t\t"string"\n\t],\n\t"tags": [],\n\t"status": "available"\n}',
|
||||||
|
},
|
||||||
|
endpoint: baseEnv + '/v2/pet',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'PUT',
|
||||||
|
requestVariables: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Validation exception': {
|
||||||
|
name: 'Validation exception',
|
||||||
|
status: 'Method Not Allowed',
|
||||||
|
code: 405,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'updatePet',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: '{\n\t"id": 1,\n\t"category": {\n\t\t"id": 1,\n\t\t"name": "string"\n\t},\n\t"name": "doggie",\n\t"photoUrls": [\n\t\t"string"\n\t],\n\t"tags": [],\n\t"status": "available"\n}',
|
||||||
|
},
|
||||||
|
endpoint: baseEnv + '/v2/pet',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'PUT',
|
||||||
|
requestVariables: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: '16',
|
||||||
|
_ref_id: `req_${generateRefId()}`,
|
||||||
|
name: 'findPetsByStatus',
|
||||||
|
method: 'GET',
|
||||||
|
endpoint: baseEnv + '/v2/pet/findByStatus',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
description:
|
||||||
|
'Status values that need to be considered for filter',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
headers: [],
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
preRequestScript: '',
|
||||||
|
testScript: '',
|
||||||
|
requestVariables: [],
|
||||||
|
responses: {
|
||||||
|
'successful operation': {
|
||||||
|
name: 'successful operation',
|
||||||
|
status: 'OK',
|
||||||
|
code: 200,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'findPetsByStatus',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/findByStatus',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
description:
|
||||||
|
'Status values that need to be considered for filter',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
headers: [],
|
||||||
|
method: 'GET',
|
||||||
|
requestVariables: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Invalid status value': {
|
||||||
|
name: 'Invalid status value',
|
||||||
|
status: 'Bad Request',
|
||||||
|
code: 400,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'findPetsByStatus',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/findByStatus',
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
description:
|
||||||
|
'Status values that need to be considered for filter',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
headers: [],
|
||||||
|
method: 'GET',
|
||||||
|
requestVariables: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: '16',
|
||||||
|
_ref_id: `req_${generateRefId()}`,
|
||||||
|
name: 'getPetById',
|
||||||
|
method: 'GET',
|
||||||
|
endpoint: baseEnv + '/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
auth: {
|
||||||
|
authType: 'api-key',
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
authActive: true,
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
preRequestScript: '',
|
||||||
|
testScript: '',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
'successful operation': {
|
||||||
|
name: 'successful operation',
|
||||||
|
status: 'OK',
|
||||||
|
code: 200,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'getPetById',
|
||||||
|
auth: {
|
||||||
|
authType: 'api-key',
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
authActive: true,
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'GET',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Invalid ID supplied': {
|
||||||
|
name: 'Invalid ID supplied',
|
||||||
|
status: 'Bad Request',
|
||||||
|
code: 400,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'getPetById',
|
||||||
|
auth: {
|
||||||
|
authType: 'api-key',
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
authActive: true,
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'GET',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Pet not found': {
|
||||||
|
name: 'Pet not found',
|
||||||
|
status: 'Not Found',
|
||||||
|
code: 404,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'getPetById',
|
||||||
|
auth: {
|
||||||
|
authType: 'api-key',
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
authActive: true,
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'GET',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: '16',
|
||||||
|
_ref_id: `req_${generateRefId()}`,
|
||||||
|
name: 'updatePetWithForm',
|
||||||
|
method: 'POST',
|
||||||
|
endpoint: baseEnv + '/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/x-www-form-urlencoded',
|
||||||
|
body: 'name: \nstatus: ',
|
||||||
|
},
|
||||||
|
preRequestScript: '',
|
||||||
|
testScript: '',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
'Invalid input': {
|
||||||
|
name: 'Invalid input',
|
||||||
|
status: 'Method Not Allowed',
|
||||||
|
code: 405,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'updatePetWithForm',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'application/x-www-form-urlencoded',
|
||||||
|
body: 'name: \nstatus: ',
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [],
|
||||||
|
method: 'POST',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: '16',
|
||||||
|
_ref_id: `req_${generateRefId()}`,
|
||||||
|
name: 'deletePet',
|
||||||
|
method: 'DELETE',
|
||||||
|
endpoint: baseEnv + '/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
preRequestScript: '',
|
||||||
|
testScript: '',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responses: {
|
||||||
|
'Invalid ID supplied': {
|
||||||
|
name: 'Invalid ID supplied',
|
||||||
|
status: 'Bad Request',
|
||||||
|
code: 400,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'deletePet',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
method: 'DELETE',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Pet not found': {
|
||||||
|
name: 'Pet not found',
|
||||||
|
status: 'Not Found',
|
||||||
|
code: 404,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'content-type',
|
||||||
|
value: 'application/json',
|
||||||
|
description: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: '',
|
||||||
|
originalRequest: {
|
||||||
|
v: '6',
|
||||||
|
name: 'deletePet',
|
||||||
|
auth: {
|
||||||
|
authType: 'oauth-2',
|
||||||
|
authActive: true,
|
||||||
|
grantTypeInfo: {
|
||||||
|
authEndpoint: baseEnv + '/oauth/authorize',
|
||||||
|
clientID: '',
|
||||||
|
grantType: 'IMPLICIT',
|
||||||
|
scopes: 'write:pets read:pets',
|
||||||
|
token: '',
|
||||||
|
authRequestParams: [],
|
||||||
|
refreshRequestParams: [],
|
||||||
|
},
|
||||||
|
addTo: 'HEADERS',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: null,
|
||||||
|
body: null,
|
||||||
|
},
|
||||||
|
endpoint: 'petstore.swagger.io/v2/pet/<<petId>>',
|
||||||
|
params: [],
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: 'api_key',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
method: 'DELETE',
|
||||||
|
requestVariables: [
|
||||||
|
{
|
||||||
|
key: 'petId',
|
||||||
|
value: '',
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: {
|
||||||
|
auth: {
|
||||||
|
authType: 'inherit',
|
||||||
|
authActive: true,
|
||||||
|
},
|
||||||
|
headers: [],
|
||||||
|
_ref_id: `coll_${generateRefId()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
@ -118,10 +118,25 @@ export class CreateMockServerInput {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Field({
|
@Field({
|
||||||
|
nullable: true,
|
||||||
description:
|
description:
|
||||||
'ID of the (team or user) collection to associate with the mock server',
|
'ID of the (team or user) collection to associate with the mock server',
|
||||||
})
|
})
|
||||||
collectionID: string;
|
collectionID?: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
nullable: true,
|
||||||
|
description:
|
||||||
|
'Whether to auto-create a collection for the mock server if collectionID is not provided',
|
||||||
|
})
|
||||||
|
autoCreateCollection?: boolean;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
nullable: true,
|
||||||
|
description:
|
||||||
|
'Whether to auto-create request examples in the collection for the mock server',
|
||||||
|
})
|
||||||
|
autoCreateRequestExample?: boolean;
|
||||||
|
|
||||||
@Field(() => WorkspaceType, {
|
@Field(() => WorkspaceType, {
|
||||||
description: 'Type of workspace: USER or TEAM',
|
description: 'Type of workspace: USER or TEAM',
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,18 @@ import { TeamModule } from 'src/team/team.module';
|
||||||
import { TeamRequestModule } from 'src/team-request/team-request.module';
|
import { TeamRequestModule } from 'src/team-request/team-request.module';
|
||||||
import { MockServerController } from './mock-server.controller';
|
import { MockServerController } from './mock-server.controller';
|
||||||
import { AccessTokenModule } from 'src/access-token/access-token.module';
|
import { AccessTokenModule } from 'src/access-token/access-token.module';
|
||||||
|
import { TeamCollectionModule } from 'src/team-collection/team-collection.module';
|
||||||
|
import { UserCollectionModule } from 'src/user-collection/user-collection.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, TeamModule, TeamRequestModule, AccessTokenModule],
|
imports: [
|
||||||
|
PrismaModule,
|
||||||
|
UserCollectionModule,
|
||||||
|
TeamModule,
|
||||||
|
TeamCollectionModule,
|
||||||
|
TeamRequestModule,
|
||||||
|
AccessTokenModule,
|
||||||
|
],
|
||||||
controllers: [MockServerController],
|
controllers: [MockServerController],
|
||||||
providers: [
|
providers: [
|
||||||
MockServerService,
|
MockServerService,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import { GqlTeamMemberGuard } from 'src/team/guards/gql-team-member.guard';
|
||||||
import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorator';
|
import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorator';
|
||||||
import { TeamAccessRole } from 'src/team/team.model';
|
import { TeamAccessRole } from 'src/team/team.model';
|
||||||
import { throwErr } from 'src/utils';
|
import { throwErr } from 'src/utils';
|
||||||
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
|
import { INVALID_PARAMS } from 'src/errors';
|
||||||
|
|
||||||
@Resolver(() => MockServer)
|
@Resolver(() => MockServer)
|
||||||
export class MockServerResolver {
|
export class MockServerResolver {
|
||||||
|
|
@ -72,7 +74,7 @@ export class MockServerResolver {
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async myMockServers(
|
async myMockServers(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args() args: OffsetPaginationArgs,
|
@Args() args: OffsetPaginationArgs,
|
||||||
): Promise<MockServer[]> {
|
): Promise<MockServer[]> {
|
||||||
return this.mockServerService.getUserMockServers(user.uid, args);
|
return this.mockServerService.getUserMockServers(user.uid, args);
|
||||||
|
|
@ -104,7 +106,7 @@ export class MockServerResolver {
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async mockServer(
|
async mockServer(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
|
|
@ -124,7 +126,7 @@ export class MockServerResolver {
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async mockServerLogs(
|
async mockServerLogs(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'mockServerID',
|
name: 'mockServerID',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
|
|
@ -151,8 +153,15 @@ export class MockServerResolver {
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async createMockServer(
|
async createMockServer(
|
||||||
@Args('input') input: CreateMockServerInput,
|
@Args('input') input: CreateMockServerInput,
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
): Promise<MockServer> {
|
): Promise<MockServer> {
|
||||||
|
if (
|
||||||
|
(input.collectionID && input.autoCreateCollection) ||
|
||||||
|
(!input.collectionID && !input.autoCreateCollection)
|
||||||
|
) {
|
||||||
|
throwErr(INVALID_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.mockServerService.createMockServer(user, input);
|
const result = await this.mockServerService.createMockServer(user, input);
|
||||||
|
|
||||||
if (E.isLeft(result)) throwErr(result.left);
|
if (E.isLeft(result)) throwErr(result.left);
|
||||||
|
|
@ -164,7 +173,7 @@ export class MockServerResolver {
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async updateMockServer(
|
async updateMockServer(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args() args: MockServerMutationArgs,
|
@Args() args: MockServerMutationArgs,
|
||||||
@Args('input') input: UpdateMockServerInput,
|
@Args('input') input: UpdateMockServerInput,
|
||||||
): Promise<MockServer> {
|
): Promise<MockServer> {
|
||||||
|
|
@ -183,7 +192,7 @@ export class MockServerResolver {
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async deleteMockServer(
|
async deleteMockServer(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args() args: MockServerMutationArgs,
|
@Args() args: MockServerMutationArgs,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const result = await this.mockServerService.deleteMockServer(
|
const result = await this.mockServerService.deleteMockServer(
|
||||||
|
|
@ -200,7 +209,7 @@ export class MockServerResolver {
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
async deleteMockServerLog(
|
async deleteMockServerLog(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'logID',
|
name: 'logID',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { MockServerService } from './mock-server.service';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { MockServerAnalyticsService } from './mock-server-analytics.service';
|
import { MockServerAnalyticsService } from './mock-server-analytics.service';
|
||||||
|
import { TeamCollectionService } from '../team-collection/team-collection.service';
|
||||||
|
import { UserCollectionService } from '../user-collection/user-collection.service';
|
||||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import {
|
import {
|
||||||
|
|
@ -17,9 +19,9 @@ import {
|
||||||
UserCollection,
|
UserCollection,
|
||||||
TeamCollection,
|
TeamCollection,
|
||||||
UserRequest,
|
UserRequest,
|
||||||
|
User,
|
||||||
} from 'src/generated/prisma/client';
|
} from 'src/generated/prisma/client';
|
||||||
import { WorkspaceType } from '../types/WorkspaceTypes';
|
import { WorkspaceType } from '../types/WorkspaceTypes';
|
||||||
import { User } from '../user/user.model';
|
|
||||||
import {
|
import {
|
||||||
CreateMockServerInput,
|
CreateMockServerInput,
|
||||||
UpdateMockServerInput,
|
UpdateMockServerInput,
|
||||||
|
|
@ -28,17 +30,23 @@ import {
|
||||||
const mockPrisma = mockDeep<PrismaService>();
|
const mockPrisma = mockDeep<PrismaService>();
|
||||||
const mockAnalyticsService = mockDeep<MockServerAnalyticsService>();
|
const mockAnalyticsService = mockDeep<MockServerAnalyticsService>();
|
||||||
const mockConfigService = mockDeep<ConfigService>();
|
const mockConfigService = mockDeep<ConfigService>();
|
||||||
|
const mockTeamCollectionService = mockDeep<TeamCollectionService>();
|
||||||
|
const mockUserCollectionService = mockDeep<UserCollectionService>();
|
||||||
|
|
||||||
const mockServerService = new MockServerService(
|
const mockServerService = new MockServerService(
|
||||||
mockAnalyticsService,
|
|
||||||
mockPrisma,
|
|
||||||
mockConfigService,
|
mockConfigService,
|
||||||
|
mockPrisma,
|
||||||
|
mockAnalyticsService,
|
||||||
|
mockTeamCollectionService,
|
||||||
|
mockUserCollectionService,
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockReset(mockPrisma);
|
mockReset(mockPrisma);
|
||||||
mockReset(mockAnalyticsService);
|
mockReset(mockAnalyticsService);
|
||||||
mockReset(mockConfigService);
|
mockReset(mockConfigService);
|
||||||
|
mockReset(mockTeamCollectionService);
|
||||||
|
mockReset(mockUserCollectionService);
|
||||||
|
|
||||||
// Default config values
|
// Default config values
|
||||||
mockConfigService.get.mockImplementation((key: string) => {
|
mockConfigService.get.mockImplementation((key: string) => {
|
||||||
|
|
@ -57,6 +65,7 @@ const user: User = {
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
photoURL: null,
|
photoURL: null,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
refreshToken: null,
|
||||||
currentGQLSession: '{}',
|
currentGQLSession: '{}',
|
||||||
currentRESTSession: '{}',
|
currentRESTSession: '{}',
|
||||||
createdOn: currentTime,
|
createdOn: currentTime,
|
||||||
|
|
@ -471,6 +480,282 @@ describe('MockServerService', () => {
|
||||||
expect(result.left).toBe('mock_server/creation_failed');
|
expect(result.left).toBe('mock_server/creation_failed');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('auto-create collection', () => {
|
||||||
|
test('should auto-create user collection without request example', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Auto Mock Server',
|
||||||
|
workspaceType: WorkspaceType.USER,
|
||||||
|
workspaceID: undefined,
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdCollection = { ...userCollection, id: 'new-coll-123' };
|
||||||
|
mockUserCollectionService.createUserCollection.mockResolvedValue(
|
||||||
|
E.right(createdCollection as any),
|
||||||
|
);
|
||||||
|
mockPrisma.mockServer.create.mockResolvedValue({
|
||||||
|
...dbMockServer,
|
||||||
|
collectionID: 'new-coll-123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isRight(result)).toBe(true);
|
||||||
|
expect(mockUserCollectionService.createUserCollection).toHaveBeenCalledWith(
|
||||||
|
user,
|
||||||
|
autoCreateInput.name,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'REST',
|
||||||
|
);
|
||||||
|
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
collectionID: 'new-coll-123',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should auto-create user collection with request example', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Auto Mock Server',
|
||||||
|
workspaceType: WorkspaceType.USER,
|
||||||
|
workspaceID: undefined,
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUserCollectionService.importCollectionsFromJSON.mockResolvedValue(
|
||||||
|
E.right({
|
||||||
|
exportedCollection: JSON.stringify([{ id: 'imported-coll-123' }]),
|
||||||
|
} as any),
|
||||||
|
);
|
||||||
|
mockPrisma.mockServer.create.mockResolvedValue({
|
||||||
|
...dbMockServer,
|
||||||
|
collectionID: 'imported-coll-123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isRight(result)).toBe(true);
|
||||||
|
expect(mockUserCollectionService.importCollectionsFromJSON).toHaveBeenCalled();
|
||||||
|
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
collectionID: 'imported-coll-123',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should auto-create team collection without request example', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Team Auto Mock',
|
||||||
|
workspaceType: WorkspaceType.TEAM,
|
||||||
|
workspaceID: 'team123',
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdTeamColl = { ...teamCollection, id: 'new-team-coll-123' };
|
||||||
|
mockPrisma.team.findFirst.mockResolvedValue({ id: 'team123' } as any);
|
||||||
|
mockTeamCollectionService.createCollection.mockResolvedValue(
|
||||||
|
E.right(createdTeamColl as any),
|
||||||
|
);
|
||||||
|
mockPrisma.mockServer.create.mockResolvedValue({
|
||||||
|
...dbMockServer,
|
||||||
|
workspaceType: WorkspaceType.TEAM,
|
||||||
|
workspaceID: 'team123',
|
||||||
|
collectionID: 'new-team-coll-123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isRight(result)).toBe(true);
|
||||||
|
expect(mockTeamCollectionService.createCollection).toHaveBeenCalledWith(
|
||||||
|
'team123',
|
||||||
|
autoCreateInput.name,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
collectionID: 'new-team-coll-123',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should auto-create team collection with request example', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Team Auto Mock',
|
||||||
|
workspaceType: WorkspaceType.TEAM,
|
||||||
|
workspaceID: 'team123',
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPrisma.team.findFirst.mockResolvedValue({ id: 'team123' } as any);
|
||||||
|
mockTeamCollectionService.importCollectionsFromJSON.mockResolvedValue(
|
||||||
|
E.right([{ id: 'imported-team-coll-123' }] as any),
|
||||||
|
);
|
||||||
|
mockPrisma.mockServer.create.mockResolvedValue({
|
||||||
|
...dbMockServer,
|
||||||
|
workspaceType: WorkspaceType.TEAM,
|
||||||
|
workspaceID: 'team123',
|
||||||
|
collectionID: 'imported-team-coll-123',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isRight(result)).toBe(true);
|
||||||
|
expect(mockTeamCollectionService.importCollectionsFromJSON).toHaveBeenCalled();
|
||||||
|
expect(mockPrisma.mockServer.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
collectionID: 'imported-team-coll-123',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return error when auto-create user collection fails', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Auto Mock Server',
|
||||||
|
workspaceType: WorkspaceType.USER,
|
||||||
|
workspaceID: undefined,
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockUserCollectionService.createUserCollection.mockResolvedValue(
|
||||||
|
E.left('user_collection/creation_failed'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isLeft(result)).toBe(true);
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
expect(result.left).toBe('user_collection/creation_failed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return error when auto-create team collection fails', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Team Auto Mock',
|
||||||
|
workspaceType: WorkspaceType.TEAM,
|
||||||
|
workspaceID: 'team123',
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPrisma.team.findFirst.mockResolvedValue({ id: 'team123' } as any);
|
||||||
|
mockTeamCollectionService.createCollection.mockResolvedValue(
|
||||||
|
E.left('team_coll/short_title'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isLeft(result)).toBe(true);
|
||||||
|
if (E.isLeft(result)) {
|
||||||
|
expect(result.left).toBe('team_coll/short_title');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should rollback collection on mock server creation failure', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Auto Mock Server',
|
||||||
|
workspaceType: WorkspaceType.USER,
|
||||||
|
workspaceID: undefined,
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdCollection = { ...userCollection, id: 'rollback-coll-123' };
|
||||||
|
mockUserCollectionService.createUserCollection.mockResolvedValue(
|
||||||
|
E.right(createdCollection as any),
|
||||||
|
);
|
||||||
|
mockPrisma.mockServer.create.mockRejectedValue(
|
||||||
|
new Error('Database error'),
|
||||||
|
);
|
||||||
|
mockUserCollectionService.deleteUserCollection.mockResolvedValue(
|
||||||
|
E.right(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isLeft(result)).toBe(true);
|
||||||
|
expect(mockUserCollectionService.deleteUserCollection).toHaveBeenCalledWith(
|
||||||
|
'rollback-coll-123',
|
||||||
|
user.uid,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should rollback team collection on mock server creation failure', async () => {
|
||||||
|
const autoCreateInput: CreateMockServerInput = {
|
||||||
|
name: 'Team Auto Mock',
|
||||||
|
workspaceType: WorkspaceType.TEAM,
|
||||||
|
workspaceID: 'team123',
|
||||||
|
delayInMs: 0,
|
||||||
|
autoCreateCollection: true,
|
||||||
|
autoCreateRequestExample: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdTeamColl = { ...teamCollection, id: 'rollback-team-coll-123' };
|
||||||
|
mockPrisma.team.findFirst.mockResolvedValue({ id: 'team123' } as any);
|
||||||
|
mockTeamCollectionService.createCollection.mockResolvedValue(
|
||||||
|
E.right(createdTeamColl as any),
|
||||||
|
);
|
||||||
|
mockPrisma.mockServer.create.mockRejectedValue(
|
||||||
|
new Error('Database error'),
|
||||||
|
);
|
||||||
|
mockTeamCollectionService.deleteCollection.mockResolvedValue(
|
||||||
|
E.right(true),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await mockServerService.createMockServer(
|
||||||
|
user,
|
||||||
|
autoCreateInput,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(E.isLeft(result)).toBe(true);
|
||||||
|
expect(mockTeamCollectionService.deleteCollection).toHaveBeenCalledWith(
|
||||||
|
'rollback-team-coll-123',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateMockServer', () => {
|
describe('updateMockServer', () => {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
MockServerCollection,
|
MockServerCollection,
|
||||||
MockServerLog,
|
MockServerLog,
|
||||||
} from './mock-server.model';
|
} from './mock-server.model';
|
||||||
import { User } from 'src/user/user.model';
|
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import {
|
import {
|
||||||
MOCK_SERVER_NOT_FOUND,
|
MOCK_SERVER_NOT_FOUND,
|
||||||
|
|
@ -19,6 +18,7 @@ import {
|
||||||
MOCK_SERVER_DELETION_FAILED,
|
MOCK_SERVER_DELETION_FAILED,
|
||||||
MOCK_SERVER_LOG_NOT_FOUND,
|
MOCK_SERVER_LOG_NOT_FOUND,
|
||||||
MOCK_SERVER_LOG_DELETION_FAILED,
|
MOCK_SERVER_LOG_DELETION_FAILED,
|
||||||
|
MOCK_SERVER_COLLECTION_CREATION_FAILED,
|
||||||
} from 'src/errors';
|
} from 'src/errors';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import { WorkspaceType } from 'src/types/WorkspaceTypes';
|
import { WorkspaceType } from 'src/types/WorkspaceTypes';
|
||||||
|
|
@ -31,13 +31,20 @@ import { OffsetPaginationArgs } from 'src/types/input-types.args';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { MockServerAnalyticsService } from './mock-server-analytics.service';
|
import { MockServerAnalyticsService } from './mock-server-analytics.service';
|
||||||
import { PrismaError } from 'src/prisma/prisma-error-codes';
|
import { PrismaError } from 'src/prisma/prisma-error-codes';
|
||||||
|
import { TeamCollectionService } from 'src/team-collection/team-collection.service';
|
||||||
|
import { UserCollectionService } from 'src/user-collection/user-collection.service';
|
||||||
|
import { ReqType } from 'src/types/RequestTypes';
|
||||||
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
|
import { mockServerCollRequestExample } from './constants/mock-server-coll-request-example';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MockServerService {
|
export class MockServerService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly mockServerAnalyticsService: MockServerAnalyticsService,
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly mockServerAnalyticsService: MockServerAnalyticsService,
|
||||||
|
private readonly teamCollectionService: TeamCollectionService,
|
||||||
|
private readonly userCollectionService: UserCollectionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -252,7 +259,10 @@ export class MockServerService {
|
||||||
/**
|
/**
|
||||||
* Validate workspace access permission and existence
|
* Validate workspace access permission and existence
|
||||||
*/
|
*/
|
||||||
private async validateWorkspace(user: User, input: CreateMockServerInput) {
|
private async validateWorkspace(
|
||||||
|
user: AuthUser,
|
||||||
|
input: CreateMockServerInput,
|
||||||
|
) {
|
||||||
if (input.workspaceType === WorkspaceType.TEAM) {
|
if (input.workspaceType === WorkspaceType.TEAM) {
|
||||||
if (!input.workspaceID) return E.left(TEAM_INVALID_ID);
|
if (!input.workspaceID) return E.left(TEAM_INVALID_ID);
|
||||||
|
|
||||||
|
|
@ -271,7 +281,12 @@ export class MockServerService {
|
||||||
/**
|
/**
|
||||||
* Validate collection exists and user has access
|
* Validate collection exists and user has access
|
||||||
*/
|
*/
|
||||||
private async validateCollection(user: User, input: CreateMockServerInput) {
|
private async validateCollection(
|
||||||
|
user: AuthUser,
|
||||||
|
input: CreateMockServerInput,
|
||||||
|
) {
|
||||||
|
if (!input.collectionID) return E.left(MOCK_SERVER_INVALID_COLLECTION);
|
||||||
|
|
||||||
if (input.workspaceType === WorkspaceType.TEAM) {
|
if (input.workspaceType === WorkspaceType.TEAM) {
|
||||||
const collection = await this.prisma.teamCollection.findUnique({
|
const collection = await this.prisma.teamCollection.findUnique({
|
||||||
where: { id: input.collectionID, teamID: input.workspaceID },
|
where: { id: input.collectionID, teamID: input.workspaceID },
|
||||||
|
|
@ -291,24 +306,105 @@ export class MockServerService {
|
||||||
return E.left(MOCK_SERVER_INVALID_COLLECTION);
|
return E.left(MOCK_SERVER_INVALID_COLLECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createAutoCollection(
|
||||||
|
user: AuthUser,
|
||||||
|
input: CreateMockServerInput,
|
||||||
|
) {
|
||||||
|
if (input.workspaceType === WorkspaceType.USER) {
|
||||||
|
if (!input.autoCreateRequestExample) {
|
||||||
|
// create only a collection
|
||||||
|
const userColl = await this.userCollectionService.createUserCollection(
|
||||||
|
user,
|
||||||
|
input.name,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ReqType.REST,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(userColl)) return E.left(userColl.left);
|
||||||
|
return E.right({ id: userColl.right.id });
|
||||||
|
} else {
|
||||||
|
// create collection with a request example
|
||||||
|
const importedUserColl =
|
||||||
|
await this.userCollectionService.importCollectionsFromJSON(
|
||||||
|
JSON.stringify(mockServerCollRequestExample(input.name)),
|
||||||
|
user.uid,
|
||||||
|
null,
|
||||||
|
ReqType.REST,
|
||||||
|
);
|
||||||
|
if (E.isLeft(importedUserColl)) return E.left(importedUserColl.left);
|
||||||
|
if (JSON.parse(importedUserColl.right.exportedCollection).length === 0)
|
||||||
|
return E.left(MOCK_SERVER_COLLECTION_CREATION_FAILED);
|
||||||
|
|
||||||
|
return E.right({
|
||||||
|
id: JSON.parse(importedUserColl.right.exportedCollection)[0].id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (input.workspaceType === WorkspaceType.TEAM) {
|
||||||
|
if (!input.workspaceID) return E.left(TEAM_INVALID_ID);
|
||||||
|
|
||||||
|
if (!input.autoCreateRequestExample) {
|
||||||
|
const teamColl = await this.teamCollectionService.createCollection(
|
||||||
|
input.workspaceID,
|
||||||
|
input.name,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(teamColl)) return E.left(teamColl.left);
|
||||||
|
return E.right({ id: teamColl.right.id });
|
||||||
|
} else {
|
||||||
|
const importedTeamColl =
|
||||||
|
await this.teamCollectionService.importCollectionsFromJSON(
|
||||||
|
JSON.stringify(mockServerCollRequestExample(input.name)),
|
||||||
|
input.workspaceID,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(importedTeamColl)) return E.left(importedTeamColl.left);
|
||||||
|
if (importedTeamColl.right.length === 0)
|
||||||
|
return E.left(MOCK_SERVER_COLLECTION_CREATION_FAILED);
|
||||||
|
|
||||||
|
return E.right({
|
||||||
|
id: importedTeamColl.right[0].id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.left(MOCK_SERVER_COLLECTION_CREATION_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new mock server
|
* Create a new mock server
|
||||||
*/
|
*/
|
||||||
async createMockServer(
|
async createMockServer(
|
||||||
user: User,
|
user: AuthUser,
|
||||||
input: CreateMockServerInput,
|
input: CreateMockServerInput,
|
||||||
): Promise<E.Either<string, MockServer>> {
|
): Promise<E.Either<string, MockServer>> {
|
||||||
|
let collectionID: string | undefined = input.collectionID;
|
||||||
try {
|
try {
|
||||||
// Validate workspace type and ID
|
// Validate workspace type and ID
|
||||||
|
|
||||||
const workspaceValidation = await this.validateWorkspace(user, input);
|
const workspaceValidation = await this.validateWorkspace(user, input);
|
||||||
if (E.isLeft(workspaceValidation)) {
|
if (E.isLeft(workspaceValidation)) {
|
||||||
return E.left(workspaceValidation.left);
|
return E.left(workspaceValidation.left);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate collection exists and user has access
|
if (!input.autoCreateCollection) {
|
||||||
const collectionValidation = await this.validateCollection(user, input);
|
// Validate collection exists and user has access
|
||||||
if (E.isLeft(collectionValidation)) {
|
const collectionValidation = await this.validateCollection(user, input);
|
||||||
return E.left(collectionValidation.left);
|
if (E.isLeft(collectionValidation)) {
|
||||||
|
return E.left(collectionValidation.left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-create collection if needed
|
||||||
|
if (input.autoCreateCollection) {
|
||||||
|
const newCollection = await this.createAutoCollection(user, input);
|
||||||
|
if (E.isLeft(newCollection)) {
|
||||||
|
return E.left(newCollection.left);
|
||||||
|
}
|
||||||
|
collectionID = newCollection.right.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mock server
|
// Create mock server
|
||||||
|
|
@ -318,7 +414,7 @@ export class MockServerService {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
subdomain,
|
subdomain,
|
||||||
creatorUid: user.uid,
|
creatorUid: user.uid,
|
||||||
collectionID: input.collectionID,
|
collectionID: input.collectionID ?? collectionID,
|
||||||
workspaceType: input.workspaceType,
|
workspaceType: input.workspaceType,
|
||||||
workspaceID:
|
workspaceID:
|
||||||
input.workspaceType === WorkspaceType.TEAM
|
input.workspaceType === WorkspaceType.TEAM
|
||||||
|
|
@ -335,9 +431,21 @@ export class MockServerService {
|
||||||
|
|
||||||
return E.right(this.cast(mockServer));
|
return E.right(this.cast(mockServer));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (input.autoCreateCollection && collectionID) {
|
||||||
|
if (input.workspaceType === WorkspaceType.USER) {
|
||||||
|
await this.userCollectionService.deleteUserCollection(
|
||||||
|
collectionID,
|
||||||
|
user.uid,
|
||||||
|
);
|
||||||
|
} else if (input.workspaceType === WorkspaceType.TEAM) {
|
||||||
|
await this.teamCollectionService.deleteCollection(collectionID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (error.code === PrismaError.UNIQUE_CONSTRAINT_VIOLATION) {
|
if (error.code === PrismaError.UNIQUE_CONSTRAINT_VIOLATION) {
|
||||||
return this.createMockServer(user, input); // Retry on subdomain conflict
|
return this.createMockServer(user, input); // Retry on subdomain conflict
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('Error creating mock server:', error);
|
console.error('Error creating mock server:', error);
|
||||||
return E.left(MOCK_SERVER_CREATION_FAILED);
|
return E.left(MOCK_SERVER_CREATION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { InputType, Field } from '@nestjs/graphql';
|
import { InputType, Field } from '@nestjs/graphql';
|
||||||
|
import { IsOptional, Matches } from 'class-validator';
|
||||||
import { WorkspaceType } from 'src/types/WorkspaceTypes';
|
import { WorkspaceType } from 'src/types/WorkspaceTypes';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
|
|
@ -13,6 +14,10 @@ export class CreatePublishedDocsArgs {
|
||||||
name: 'version',
|
name: 'version',
|
||||||
description: 'Version of the published document',
|
description: 'Version of the published document',
|
||||||
})
|
})
|
||||||
|
@Matches(/^[a-zA-Z0-9.-]+$/, {
|
||||||
|
message:
|
||||||
|
'Version must only contain alphanumeric characters, dots, and hyphens',
|
||||||
|
})
|
||||||
version: string;
|
version: string;
|
||||||
|
|
||||||
@Field({
|
@Field({
|
||||||
|
|
@ -62,6 +67,11 @@ export class UpdatePublishedDocsArgs {
|
||||||
description: 'Version of the published document',
|
description: 'Version of the published document',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@Matches(/^[a-zA-Z0-9.-]+$/, {
|
||||||
|
message:
|
||||||
|
'Version must only contain alphanumeric characters, dots, and hyphens',
|
||||||
|
})
|
||||||
version?: string;
|
version?: string;
|
||||||
|
|
||||||
@Field({
|
@Field({
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ export class TeamCollectionResolver {
|
||||||
parentCollectionID ?? null,
|
parentCollectionID ?? null,
|
||||||
);
|
);
|
||||||
if (E.isLeft(importedCollection)) throwErr(importedCollection.left);
|
if (E.isLeft(importedCollection)) throwErr(importedCollection.left);
|
||||||
return importedCollection.right;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamCollection, {
|
@Mutation(() => TeamCollection, {
|
||||||
|
|
|
||||||
|
|
@ -1397,7 +1397,7 @@ describe('importCollectionsFromJSON', () => {
|
||||||
rootTeamCollection.teamID,
|
rootTeamCollection.teamID,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
expect(result).toEqualRight(true);
|
expect(result).toEqualRight([rootTeamCollection]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should successfully create new TeamCollections in a child collection and TeamRequests with valid inputs', async () => {
|
test('should successfully create new TeamCollections in a child collection and TeamRequests with valid inputs', async () => {
|
||||||
|
|
@ -1410,7 +1410,7 @@ describe('importCollectionsFromJSON', () => {
|
||||||
rootTeamCollection.teamID,
|
rootTeamCollection.teamID,
|
||||||
rootTeamCollection.id,
|
rootTeamCollection.id,
|
||||||
);
|
);
|
||||||
expect(result).toEqualRight(true);
|
expect(result).toEqualRight([rootTeamCollection]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should send pubsub message to "team_coll/<teamID>/coll_added" on successful creation from jsonString', async () => {
|
test('should send pubsub message to "team_coll/<teamID>/coll_added" on successful creation from jsonString', async () => {
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ export class TeamCollectionService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return E.right(true);
|
return E.right(teamCollections);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1083,6 +1083,8 @@
|
||||||
"environment_variable_added": "Mock URL added to environment",
|
"environment_variable_added": "Mock URL added to environment",
|
||||||
"environment_variable_updated": "Mock URL updated in environment",
|
"environment_variable_updated": "Mock URL updated in environment",
|
||||||
"environment_created_with_variable": "Environment created with mock URL",
|
"environment_created_with_variable": "Environment created with mock URL",
|
||||||
|
"add_example_request": "Add example request",
|
||||||
|
"add_example_request_hint": "The collection will be created with a sample request that demonstrates how to use the mock server",
|
||||||
"create_example_collection": "Create example collection",
|
"create_example_collection": "Create example collection",
|
||||||
"create_example_collection_hint": "Create a pet store example collection with sample requests (GET, POST, PUT, DELETE)",
|
"create_example_collection_hint": "Create a pet store example collection with sample requests (GET, POST, PUT, DELETE)",
|
||||||
"creating_example_collection": "Creating example collection...",
|
"creating_example_collection": "Creating example collection...",
|
||||||
|
|
|
||||||
|
|
@ -186,24 +186,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create Example Collection Toggle (only when "new collection" is selected) -->
|
<!-- Auto-create Request Example Toggle (only for new collection mode) -->
|
||||||
<div
|
<div
|
||||||
v-if="collectionSelectionMode === 'new'"
|
v-if="collectionSelectionMode === 'new'"
|
||||||
class="flex flex-col space-y-2"
|
class="flex flex-col space-y-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<HoppSmartToggle
|
<HoppSmartToggle
|
||||||
:on="createExampleCollection"
|
:on="autoCreateRequestExample"
|
||||||
@change="createExampleCollection = !createExampleCollection"
|
@change="autoCreateRequestExample = !autoCreateRequestExample"
|
||||||
>
|
>
|
||||||
{{ t("mock_server.create_example_collection") }}
|
{{ t("mock_server.add_example_request") }}
|
||||||
</HoppSmartToggle>
|
</HoppSmartToggle>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="createExampleCollection"
|
v-if="autoCreateRequestExample"
|
||||||
class="w-full text-xs text-secondaryLight"
|
class="w-full text-xs text-secondaryLight"
|
||||||
>
|
>
|
||||||
{{ t("mock_server.create_example_collection_hint") }}
|
{{ t("mock_server.add_example_request_hint") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -276,9 +276,7 @@
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="
|
:disabled="
|
||||||
!mockServerName.trim() ||
|
!mockServerName.trim() ||
|
||||||
(!effectiveCollectionID &&
|
(!effectiveCollectionID && collectionSelectionMode === 'existing')
|
||||||
collectionSelectionMode === 'existing') ||
|
|
||||||
(collectionSelectionMode === 'new' && !createExampleCollection)
|
|
||||||
"
|
"
|
||||||
:icon="IconServer"
|
:icon="IconServer"
|
||||||
@click="handleCreateMockServer"
|
@click="handleCreateMockServer"
|
||||||
|
|
@ -300,17 +298,10 @@ import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { TippyComponent } from "vue-tippy"
|
import { TippyComponent } from "vue-tippy"
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
import { MockServer } from "~/helpers/backend/graphql"
|
import { MockServer } from "~/helpers/backend/graphql"
|
||||||
import { showCreateMockServerModal$ } from "~/newstore/mockServers"
|
import { showCreateMockServerModal$ } from "~/newstore/mockServers"
|
||||||
import { useMockServer } from "~/composables/useMockServer"
|
import { useMockServer } from "~/composables/useMockServer"
|
||||||
import MockServerCreatedInfo from "~/components/mockServer/MockServerCreatedInfo.vue"
|
import MockServerCreatedInfo from "~/components/mockServer/MockServerCreatedInfo.vue"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import {
|
|
||||||
createMockCollectionForTeam,
|
|
||||||
createMockCollectionForPersonal,
|
|
||||||
} from "~/helpers/mockServer/exampleMockCollection"
|
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
|
@ -330,12 +321,6 @@ const {
|
||||||
toggleMockServer,
|
toggleMockServer,
|
||||||
} = useMockServer()
|
} = useMockServer()
|
||||||
|
|
||||||
// Services
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
|
||||||
|
|
||||||
// Current workspace
|
|
||||||
const currentWorkspace = computed(() => workspaceService.currentWorkspace.value)
|
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
const modalData = useReadonlyStream(showCreateMockServerModal$, {
|
const modalData = useReadonlyStream(showCreateMockServerModal$, {
|
||||||
show: false,
|
show: false,
|
||||||
|
|
@ -350,7 +335,7 @@ const createdServer = ref<MockServer | null>(null)
|
||||||
const delayInMsVal = ref<string>("0")
|
const delayInMsVal = ref<string>("0")
|
||||||
const isPublic = ref<boolean>(true)
|
const isPublic = ref<boolean>(true)
|
||||||
const setInEnvironment = ref<boolean>(true)
|
const setInEnvironment = ref<boolean>(true)
|
||||||
const createExampleCollection = ref<boolean>(false)
|
const autoCreateRequestExample = ref<boolean>(true)
|
||||||
const selectedCollectionID = ref("")
|
const selectedCollectionID = ref("")
|
||||||
const selectedCollectionName = ref("")
|
const selectedCollectionName = ref("")
|
||||||
const tippyActions = ref<TippyComponent | null>(null)
|
const tippyActions = ref<TippyComponent | null>(null)
|
||||||
|
|
@ -388,6 +373,8 @@ const effectiveCollectionID = computed(() => {
|
||||||
// Get collection name
|
// Get collection name
|
||||||
const collectionName = computed(() => {
|
const collectionName = computed(() => {
|
||||||
if (selectedCollectionName.value) return selectedCollectionName.value
|
if (selectedCollectionName.value) return selectedCollectionName.value
|
||||||
|
// When creating new collection, use the mock server name as collection name
|
||||||
|
if (collectionSelectionMode.value === "new") return mockServerName.value
|
||||||
return "Unknown Collection"
|
return "Unknown Collection"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -402,42 +389,6 @@ const selectCollection = (option: any) => {
|
||||||
selectedCollectionName.value = option.label
|
selectedCollectionName.value = option.label
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to create an example collection and return its ID and name
|
|
||||||
const createExampleCollectionAndGetID = async (
|
|
||||||
collectionName: string
|
|
||||||
): Promise<{
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}> => {
|
|
||||||
const workspaceType = currentWorkspace.value.type
|
|
||||||
|
|
||||||
if (workspaceType === "personal") {
|
|
||||||
// For personal workspace
|
|
||||||
const result = await createMockCollectionForPersonal(collectionName)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
throw new Error(result.left)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.right
|
|
||||||
} else if (workspaceType === "team" && currentWorkspace.value.teamID) {
|
|
||||||
// For team workspace
|
|
||||||
const teamID = currentWorkspace.value.teamID
|
|
||||||
const result = await createMockCollectionForTeam(teamID, collectionName)
|
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
|
||||||
throw new Error(result.left)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a bit for the subscription to update
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
return result.right
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Unknown workspace type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new mock server
|
// Create new mock server
|
||||||
const handleCreateMockServer = async () => {
|
const handleCreateMockServer = async () => {
|
||||||
// Validate mock server name first
|
// Validate mock server name first
|
||||||
|
|
@ -446,53 +397,28 @@ const handleCreateMockServer = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start loading and show creating message
|
// For existing collection mode, validate that a collection is selected
|
||||||
loading.value = true
|
if (
|
||||||
|
collectionSelectionMode.value === "existing" &&
|
||||||
// If "new collection" mode is selected, create example collection (if toggle is enabled)
|
!effectiveCollectionID.value
|
||||||
let collectionIDToUse = effectiveCollectionID.value
|
) {
|
||||||
|
|
||||||
if (collectionSelectionMode.value === "new") {
|
|
||||||
if (createExampleCollection.value) {
|
|
||||||
try {
|
|
||||||
// Silently create the collection in the background
|
|
||||||
const newCollection = await createExampleCollectionAndGetID(
|
|
||||||
mockServerName.value.trim()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update the selected collection with the actual created collection's ID and name
|
|
||||||
collectionIDToUse = newCollection.id
|
|
||||||
selectedCollectionID.value = newCollection.id
|
|
||||||
selectedCollectionName.value = newCollection.name
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to create collection:", error)
|
|
||||||
// If collection creation fails, stop the entire process
|
|
||||||
toast.error(t("mock_server.failed_to_create_mock_server"))
|
|
||||||
loading.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If new collection mode but example collection is not enabled
|
|
||||||
toast.error(t("mock_server.enable_example_collection_hint"))
|
|
||||||
loading.value = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate collection ID
|
|
||||||
if (!collectionIDToUse) {
|
|
||||||
toast.error(t("mock_server.select_collection_error"))
|
toast.error(t("mock_server.select_collection_error"))
|
||||||
loading.value = false
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait a bit more to ensure collection is fully available in the system
|
// Start loading
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
loading.value = true
|
||||||
|
|
||||||
|
// Determine if we should auto-create a collection
|
||||||
|
const isNewCollectionMode = collectionSelectionMode.value === "new"
|
||||||
|
|
||||||
// Now create the mock server
|
// Now create the mock server
|
||||||
const result = await createMockServer({
|
const result = await createMockServer({
|
||||||
mockServerName: mockServerName.value,
|
mockServerName: mockServerName.value,
|
||||||
collectionID: collectionIDToUse,
|
collectionID: isNewCollectionMode ? undefined : effectiveCollectionID.value,
|
||||||
|
autoCreateCollection: isNewCollectionMode ? true : undefined,
|
||||||
|
autoCreateRequestExample:
|
||||||
|
isNewCollectionMode && autoCreateRequestExample.value ? true : undefined,
|
||||||
delayInMs: Number(delayInMsVal.value) || 0,
|
delayInMs: Number(delayInMsVal.value) || 0,
|
||||||
isPublic: isPublic.value,
|
isPublic: isPublic.value,
|
||||||
setInEnvironment: setInEnvironment.value,
|
setInEnvironment: setInEnvironment.value,
|
||||||
|
|
@ -503,6 +429,12 @@ const handleCreateMockServer = async () => {
|
||||||
|
|
||||||
if (result.success && result.server) {
|
if (result.success && result.server) {
|
||||||
createdServer.value = result.server
|
createdServer.value = result.server
|
||||||
|
|
||||||
|
// Update the selected collection info from the created server
|
||||||
|
if (result.server.collection) {
|
||||||
|
selectedCollectionID.value = result.server.collection.id
|
||||||
|
selectedCollectionName.value = result.server.collection.title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,19 +471,12 @@ watch(show, (newShow) => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
delayInMsVal.value = "0"
|
delayInMsVal.value = "0"
|
||||||
isPublic.value = true
|
isPublic.value = true
|
||||||
|
autoCreateRequestExample.value = true
|
||||||
setInEnvironment.value = true
|
setInEnvironment.value = true
|
||||||
createExampleCollection.value = false
|
|
||||||
selectedCollectionID.value = ""
|
selectedCollectionID.value = ""
|
||||||
selectedCollectionName.value = ""
|
selectedCollectionName.value = ""
|
||||||
createdServer.value = null
|
createdServer.value = null
|
||||||
collectionSelectionMode.value = "existing"
|
collectionSelectionMode.value = "existing"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Auto-enable example collection toggle when switching to "new" mode
|
|
||||||
watch(collectionSelectionMode, (newMode) => {
|
|
||||||
if (newMode === "new") {
|
|
||||||
createExampleCollection.value = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,11 @@ import {
|
||||||
addMockServer,
|
addMockServer,
|
||||||
mockServers$,
|
mockServers$,
|
||||||
updateMockServer as updateMockServerInStore,
|
updateMockServer as updateMockServerInStore,
|
||||||
|
loadMockServers,
|
||||||
} from "~/newstore/mockServers"
|
} from "~/newstore/mockServers"
|
||||||
import { TeamCollectionsService } from "~/services/team-collection.service"
|
import { TeamCollectionsService } from "~/services/team-collection.service"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
export function useMockServer() {
|
export function useMockServer() {
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
@ -62,6 +64,30 @@ export function useMockServer() {
|
||||||
: undefined
|
: undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Function to refetch collections and mock servers
|
||||||
|
const refetchData = async () => {
|
||||||
|
try {
|
||||||
|
// Refetch mock servers
|
||||||
|
await loadMockServers()
|
||||||
|
|
||||||
|
// Refetch collections based on workspace type
|
||||||
|
if (
|
||||||
|
currentWorkspace.value.type === "team" &&
|
||||||
|
currentWorkspace.value.teamID
|
||||||
|
) {
|
||||||
|
// For team workspace, reload team collections by re-initializing with the same team ID
|
||||||
|
teamCollectionsService.changeTeamID(currentWorkspace.value.teamID)
|
||||||
|
} else {
|
||||||
|
// For personal workspace, load REST collections only (mock servers are REST-based)
|
||||||
|
if (platform.sync.collections.loadUserCollections) {
|
||||||
|
await platform.sync.collections.loadUserCollections("REST")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to refetch data:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to add mock URL to environment
|
// Function to add mock URL to environment
|
||||||
const addMockUrlToEnvironment = async (
|
const addMockUrlToEnvironment = async (
|
||||||
mockUrl: string,
|
mockUrl: string,
|
||||||
|
|
@ -190,7 +216,9 @@ export function useMockServer() {
|
||||||
// Create new mock server
|
// Create new mock server
|
||||||
const createMockServer = async (params: {
|
const createMockServer = async (params: {
|
||||||
mockServerName: string
|
mockServerName: string
|
||||||
collectionID: string
|
collectionID?: string
|
||||||
|
autoCreateCollection?: boolean
|
||||||
|
autoCreateRequestExample?: boolean
|
||||||
delayInMs: number
|
delayInMs: number
|
||||||
isPublic: boolean
|
isPublic: boolean
|
||||||
setInEnvironment: boolean
|
setInEnvironment: boolean
|
||||||
|
|
@ -199,16 +227,24 @@ export function useMockServer() {
|
||||||
const {
|
const {
|
||||||
mockServerName,
|
mockServerName,
|
||||||
collectionID,
|
collectionID,
|
||||||
|
autoCreateCollection,
|
||||||
|
autoCreateRequestExample,
|
||||||
delayInMs,
|
delayInMs,
|
||||||
isPublic,
|
isPublic,
|
||||||
setInEnvironment,
|
setInEnvironment,
|
||||||
collectionName,
|
collectionName,
|
||||||
} = params
|
} = params
|
||||||
|
|
||||||
if (!mockServerName.trim() || !collectionID) {
|
if (!mockServerName.trim()) {
|
||||||
if (!collectionID) {
|
return { success: false, server: null }
|
||||||
toast.error(t("mock_server.select_collection_error"))
|
}
|
||||||
}
|
|
||||||
|
// Exactly one of collectionID or autoCreateCollection must be provided (XOR)
|
||||||
|
if (
|
||||||
|
(!collectionID && !autoCreateCollection) ||
|
||||||
|
(collectionID && autoCreateCollection)
|
||||||
|
) {
|
||||||
|
toast.error(t("mock_server.select_collection_error"))
|
||||||
return { success: false, server: null }
|
return { success: false, server: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,11 +261,13 @@ export function useMockServer() {
|
||||||
const result = await pipe(
|
const result = await pipe(
|
||||||
createMockServerMutation(
|
createMockServerMutation(
|
||||||
mockServerName.trim(),
|
mockServerName.trim(),
|
||||||
collectionID,
|
|
||||||
workspaceType,
|
workspaceType,
|
||||||
workspaceID,
|
workspaceID,
|
||||||
delayInMs,
|
delayInMs,
|
||||||
isPublic
|
isPublic,
|
||||||
|
collectionID,
|
||||||
|
autoCreateCollection,
|
||||||
|
autoCreateRequestExample
|
||||||
),
|
),
|
||||||
TE.match(
|
TE.match(
|
||||||
(error) => {
|
(error) => {
|
||||||
|
|
@ -258,6 +296,9 @@ export function useMockServer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refetch collections and mock servers to get the latest data
|
||||||
|
await refetchData()
|
||||||
|
|
||||||
return { success: true, server: result }
|
return { success: true, server: result }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,13 @@ type DeleteMockServerError =
|
||||||
|
|
||||||
export const createMockServer = (
|
export const createMockServer = (
|
||||||
name: string,
|
name: string,
|
||||||
collectionID: string,
|
|
||||||
workspaceType: WorkspaceType = WorkspaceType.User,
|
workspaceType: WorkspaceType = WorkspaceType.User,
|
||||||
workspaceID?: string,
|
workspaceID?: string,
|
||||||
delayInMs: number = 0,
|
delayInMs: number = 0,
|
||||||
isPublic: boolean = true
|
isPublic: boolean = true,
|
||||||
|
collectionID?: string,
|
||||||
|
autoCreateCollection?: boolean,
|
||||||
|
autoCreateRequestExample?: boolean
|
||||||
) =>
|
) =>
|
||||||
TE.tryCatch(
|
TE.tryCatch(
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -67,6 +69,8 @@ export const createMockServer = (
|
||||||
input: {
|
input: {
|
||||||
name,
|
name,
|
||||||
collectionID,
|
collectionID,
|
||||||
|
autoCreateCollection,
|
||||||
|
autoCreateRequestExample,
|
||||||
workspaceType,
|
workspaceType,
|
||||||
workspaceID,
|
workspaceID,
|
||||||
delayInMs,
|
delayInMs,
|
||||||
|
|
@ -107,7 +111,7 @@ export const createMockServer = (
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
userUid: data.creator?.uid || "", // Legacy field
|
userUid: data.creator?.uid || "", // Legacy field
|
||||||
collectionID: data.collection?.id || collectionID, // Legacy field
|
collectionID: data.collection?.id || collectionID || "", // Legacy field - use response collection ID if available
|
||||||
} as MockServer
|
} as MockServer
|
||||||
},
|
},
|
||||||
(error) => (error as Error).message as CreateMockServerError
|
(error) => (error as Error).message as CreateMockServerError
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import * as E from "fp-ts/Either"
|
||||||
|
|
||||||
export type CollectionsPlatformDef = {
|
export type CollectionsPlatformDef = {
|
||||||
initCollectionsSync: () => void
|
initCollectionsSync: () => void
|
||||||
|
loadUserCollections?: (collectionType: "REST" | "GQL") => Promise<void>
|
||||||
importToPersonalWorkspace?: (
|
importToPersonalWorkspace?: (
|
||||||
collections: HoppCollection[],
|
collections: HoppCollection[],
|
||||||
reqType: ReqType
|
reqType: ReqType
|
||||||
|
|
|
||||||
|
|
@ -1032,6 +1032,7 @@ import { importToPersonalWorkspace } from "./import"
|
||||||
|
|
||||||
export const def: CollectionsPlatformDef = {
|
export const def: CollectionsPlatformDef = {
|
||||||
initCollectionsSync,
|
initCollectionsSync,
|
||||||
|
loadUserCollections,
|
||||||
importToPersonalWorkspace,
|
importToPersonalWorkspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1032,6 +1032,7 @@ function setupUserRequestDeletedSubscription() {
|
||||||
|
|
||||||
export const def: CollectionsPlatformDef = {
|
export const def: CollectionsPlatformDef = {
|
||||||
initCollectionsSync,
|
initCollectionsSync,
|
||||||
|
loadUserCollections,
|
||||||
importToPersonalWorkspace,
|
importToPersonalWorkspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue