@operato/graphql 10.29.76 → 10.29.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [10.29.79](https://github.com/hatiolab/operato/compare/v10.29.78...v10.29.79) (2026-11-10)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * **graphql:** a request counts down exactly once - answered, failed, aborted or left ([7dcb784](https://github.com/hatiolab/operato/commit/7dcb78480feee4c941a1c59937d6c5651be7dc56))
12
+
13
+
14
+
6
15
  ### [10.29.76](https://github.com/hatiolab/operato/compare/v10.29.75...v10.29.76) (2026-11-09)
7
16
 
8
17
  **Note:** Version bump only for package @operato/graphql
@@ -1,4 +1,9 @@
1
1
  import { ApolloLink } from '@apollo/client/core/index.js';
2
+ /**
3
+ * Counts a request up when it goes out and down exactly once when it ends - answered, failed, refused, aborted, or
4
+ * left by the screen that asked (an unsubscribe: a query controller reading other variables, a host that left). The
5
+ * shell shows its progress bar while the count is not zero (shell/actions/busy.ts).
6
+ */
2
7
  export declare const activeRequestCounterLink: ApolloLink;
3
8
  export declare function setActiveRequestCounterCallback(newCallback: (activeRequestCount: number) => void): void;
4
9
  export declare function resetActiveRequestCounterCallback(): void;
@@ -1,11 +1,36 @@
1
- import { ApolloLink } from '@apollo/client/core/index.js';
1
+ import { ApolloLink, Observable } from '@apollo/client/core/index.js';
2
2
  var activeRequests = 0;
3
3
  var callback = null;
4
+ /**
5
+ * Counts a request up when it goes out and down exactly once when it ends - answered, failed, refused, aborted, or
6
+ * left by the screen that asked (an unsubscribe: a query controller reading other variables, a host that left). The
7
+ * shell shows its progress bar while the count is not zero (shell/actions/busy.ts).
8
+ */
4
9
  export const activeRequestCounterLink = new ApolloLink((operation, forward) => {
5
- callback && callback(++activeRequests);
6
- return forward(operation).map(response => {
7
- callback && callback(--activeRequests);
8
- return response;
10
+ return new Observable(observer => {
11
+ increaseActiveRequestCounter();
12
+ let ended = false;
13
+ const end = () => {
14
+ if (ended)
15
+ return;
16
+ ended = true;
17
+ decreaseActiveRequestCounter();
18
+ };
19
+ const subscription = forward(operation).subscribe({
20
+ next: result => observer.next(result),
21
+ error: error => {
22
+ end();
23
+ observer.error(error);
24
+ },
25
+ complete: () => {
26
+ end();
27
+ observer.complete();
28
+ }
29
+ });
30
+ return () => {
31
+ end();
32
+ subscription.unsubscribe();
33
+ };
9
34
  });
10
35
  });
11
36
  export function setActiveRequestCounterCallback(newCallback) {
@@ -1 +1 @@
1
- {"version":3,"file":"active-request-counter-link.js","sourceRoot":"","sources":["../../src/active-request-counter-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAEzD,IAAI,cAAc,GAAG,CAAC,CAAA;AAEtB,IAAI,QAAQ,GAAkD,IAAI,CAAA;AAElE,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,UAAU,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE;IAC5E,QAAQ,IAAI,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAA;IAEtC,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;QACvC,QAAQ,IAAI,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAA;QAEtC,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,MAAM,UAAU,+BAA+B,CAAC,WAAiD;IAC/F,QAAQ,GAAG,WAAW,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,iCAAiC;IAC/C,QAAQ,GAAG,IAAI,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,QAAQ,IAAI,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,QAAQ,IAAI,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAA;AACxC,CAAC","sourcesContent":["import { ApolloLink } from '@apollo/client/core/index.js'\n\nvar activeRequests = 0\n\nvar callback: ((activeRequestCount: number) => void) | null = null\n\nexport const activeRequestCounterLink = new ApolloLink((operation, forward) => {\n callback && callback(++activeRequests)\n\n return forward(operation).map(response => {\n callback && callback(--activeRequests)\n\n return response\n })\n})\n\nexport function setActiveRequestCounterCallback(newCallback: (activeRequestCount: number) => void) {\n callback = newCallback\n}\n\nexport function resetActiveRequestCounterCallback() {\n callback = null\n}\n\nexport function decreaseActiveRequestCounter() {\n callback && callback(--activeRequests)\n}\n\nexport function increaseActiveRequestCounter() {\n callback && callback(++activeRequests)\n}\n"]}
1
+ {"version":3,"file":"active-request-counter-link.js","sourceRoot":"","sources":["../../src/active-request-counter-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AAErE,IAAI,cAAc,GAAG,CAAC,CAAA;AAEtB,IAAI,QAAQ,GAAkD,IAAI,CAAA;AAElE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,UAAU,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE;IAC5E,OAAO,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC/B,4BAA4B,EAAE,CAAA;QAE9B,IAAI,KAAK,GAAG,KAAK,CAAA;QACjB,MAAM,GAAG,GAAG,GAAG,EAAE;YACf,IAAI,KAAK;gBAAE,OAAM;YACjB,KAAK,GAAG,IAAI,CAAA;YACZ,4BAA4B,EAAE,CAAA;QAChC,CAAC,CAAA;QAED,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC;YAChD,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;YACrC,KAAK,EAAE,KAAK,CAAC,EAAE;gBACb,GAAG,EAAE,CAAA;gBACL,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACvB,CAAC;YACD,QAAQ,EAAE,GAAG,EAAE;gBACb,GAAG,EAAE,CAAA;gBACL,QAAQ,CAAC,QAAQ,EAAE,CAAA;YACrB,CAAC;SACF,CAAC,CAAA;QAEF,OAAO,GAAG,EAAE;YACV,GAAG,EAAE,CAAA;YACL,YAAY,CAAC,WAAW,EAAE,CAAA;QAC5B,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,MAAM,UAAU,+BAA+B,CAAC,WAAiD;IAC/F,QAAQ,GAAG,WAAW,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,iCAAiC;IAC/C,QAAQ,GAAG,IAAI,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,QAAQ,IAAI,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,QAAQ,IAAI,QAAQ,CAAC,EAAE,cAAc,CAAC,CAAA;AACxC,CAAC","sourcesContent":["import { ApolloLink, Observable } from '@apollo/client/core/index.js'\n\nvar activeRequests = 0\n\nvar callback: ((activeRequestCount: number) => void) | null = null\n\n/**\n * Counts a request up when it goes out and down exactly once when it ends - answered, failed, refused, aborted, or\n * left by the screen that asked (an unsubscribe: a query controller reading other variables, a host that left). The\n * shell shows its progress bar while the count is not zero (shell/actions/busy.ts).\n */\nexport const activeRequestCounterLink = new ApolloLink((operation, forward) => {\n return new Observable(observer => {\n increaseActiveRequestCounter()\n\n let ended = false\n const end = () => {\n if (ended) return\n ended = true\n decreaseActiveRequestCounter()\n }\n\n const subscription = forward(operation).subscribe({\n next: result => observer.next(result),\n error: error => {\n end()\n observer.error(error)\n },\n complete: () => {\n end()\n observer.complete()\n }\n })\n\n return () => {\n end()\n subscription.unsubscribe()\n }\n })\n})\n\nexport function setActiveRequestCounterCallback(newCallback: (activeRequestCount: number) => void) {\n callback = newCallback\n}\n\nexport function resetActiveRequestCounterCallback() {\n callback = null\n}\n\nexport function decreaseActiveRequestCounter() {\n callback && callback(--activeRequests)\n}\n\nexport function increaseActiveRequestCounter() {\n callback && callback(++activeRequests)\n}\n"]}
@@ -7,7 +7,7 @@ import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename
7
7
  import { GraphQLWsLink } from '@apollo/client/link/subscriptions/index.js';
8
8
  import { getMainDefinition } from '@apollo/client/utilities/index.js';
9
9
  import { decodeHTML } from '@operato/utils';
10
- import { activeRequestCounterLink, decreaseActiveRequestCounter } from './active-request-counter-link.js';
10
+ import { activeRequestCounterLink } from './active-request-counter-link.js';
11
11
  import { forbiddenMeaning, sayAddressRefused } from './forbidden.js';
12
12
  import { uploadFetch } from './upload-progress.js';
13
13
  export const GRAPHQL_URI = '/graphql';
@@ -61,7 +61,6 @@ const ERROR_HANDLER = ({ operation, graphQLErrors, networkError }) => {
61
61
  }));
62
62
  }
63
63
  if (networkError) {
64
- decreaseActiveRequestCounter();
65
64
  const shownByScreen = errorsShownByScreen(operation.getContext());
66
65
  /* A read its page replaced or left is aborted on purpose (PageView.reads, ADR-0190): nothing failed. */
67
66
  if (networkError.name === 'AbortError')
@@ -1 +1 @@
1
- {"version":3,"file":"graphql-env.js","sourceRoot":"","sources":["../../src/graphql-env.ts"],"names":[],"mappings":"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","sourcesContent":["import createUploadLink from 'apollo-upload-client/createUploadLink.mjs'\nimport { createClient as createWebSocketClient } from 'graphql-ws'\n\nimport {\n ApolloLink,\n from,\n HttpLink,\n HttpOptions,\n Observable,\n ServerParseError,\n split\n} from '@apollo/client/core/index.js'\nimport { ErrorLink, onError } from '@apollo/client/link/error/index.js'\nimport { errorsShownByScreen, isHandledRefusal } from './handled-refusal.js'\nimport { removeTypenameFromVariables } from '@apollo/client/link/remove-typename/index.js'\nimport { GraphQLWsLink } from '@apollo/client/link/subscriptions/index.js'\nimport { getMainDefinition } from '@apollo/client/utilities/index.js'\n\nimport { decodeHTML } from '@operato/utils'\nimport { activeRequestCounterLink, decreaseActiveRequestCounter } from './active-request-counter-link.js'\nimport { forbiddenMeaning, sayAddressRefused } from './forbidden.js'\nimport { uploadFetch } from './upload-progress.js'\n\nexport const GRAPHQL_URI = '/graphql'\nexport const SUBSCRIPTION_URI = GRAPHQL_URI\n\nvar apolloLink: ApolloLink\n\nconst httpOptions: HttpOptions = {\n uri: GRAPHQL_URI,\n credentials: 'include',\n headers: { 'Apollo-Require-Preflight': 'true' }\n}\n\nconst httpLink = ApolloLink.split(\n operation => operation.getContext().hasUpload,\n /* uploadFetch: an upload that asks for progress goes through XHR; everything else is plain fetch. */\n createUploadLink({ ...httpOptions, fetch: uploadFetch }) as any,\n new HttpLink(httpOptions)\n)\n\nconst wsLink = new GraphQLWsLink(\n createWebSocketClient({\n url: location.origin.replace(/^http/, 'ws') + SUBSCRIPTION_URI,\n keepAlive: 10_000,\n retryAttempts: 1_000_000,\n shouldRetry: e => true,\n connectionParams: {\n headers: {\n /* \n 특정 도메인의 데이타만 받고자 하는 경우에, referer 정보를 제공해서 서버에서 서브도메인 정보를 취득하도록 한다.\n referer: location.href\n 또는, 이미 서브도메인 정보를 알고 있다면,\n 'x-things-factory-domain': '[subdomain]'\n 을 보낼 수 있다.\n 관련 정보를 보내지 않는다면, 사용자가 권한을 가진 모든 도메인의 데이타를 수신하게 된다.\n */\n referer: location.href\n }\n }\n })\n)\n\nconst ERROR_HANDLER: ErrorLink.ErrorHandler = ({ operation, graphQLErrors, networkError }) => {\n /* A screen that names this refusal says it in its own body (handled-refusal.ts). */\n if (graphQLErrors && !isHandledRefusal(operation.getContext(), graphQLErrors)) {\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: decodeHTML(graphQLErrors[0].message),\n ex: graphQLErrors,\n /*\n * Closed by the snackbar's own ✕. A 'Close' action here did the same thing twice and took the one\n * button slot - a package that knows what an error means (auth-ui, for a refusal a role would open)\n * fills it through registerNotifyAction. This link reports errors; it does not interpret them.\n */\n option: {\n timer: -1 // disable auto close\n }\n }\n })\n )\n }\n\n if (networkError) {\n decreaseActiveRequestCounter()\n const shownByScreen = errorsShownByScreen(operation.getContext())\n\n /* A read its page replaced or left is aborted on purpose (PageView.reads, ADR-0190): nothing failed. */\n if (networkError.name === 'AbortError') return\n\n /* networkError가 ServerParseError 이거나 ServerError 인 경우에만 statusCode를 갖는다. */\n switch ((networkError as ServerParseError).statusCode) {\n case undefined /* in case this error is not a server side error */:\n if (shownByScreen) break\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: networkError.message,\n ex: networkError\n }\n })\n )\n break\n\n case 401:\n /* 401 에러가 리턴되면, 인증이 필요하다는 메시지를 dispatch 한다. 이 auth 모듈 등에서 이 메시지를 받아서 signin 프로세스를 진행할 수 있다. */\n document.dispatchEvent(new CustomEvent('auth-required'))\n break\n\n case 403:\n /*\n * 403 은 둘이다 — 도메인이 아직 없는 것과, 이 주소를 사업장이 받지 않는 것(forbidden.ts).\n * 앞의 것은 domain-required 를 보내 auth 가 체크인으로 보낸다. 뒤의 것은 체크인이 풀 수 없으므로\n * 보내지 않고 한 번 말한다 — 보내면 체크인과 화면 사이를 돈다.\n */\n if (forbiddenMeaning((networkError as ServerParseError).response?.headers) === 'address-refused') {\n sayAddressRefused()\n } else {\n document.dispatchEvent(new CustomEvent('domain-required'))\n }\n break\n\n default:\n if (shownByScreen) break\n var { name, response, statusCode, bodyText, message } = networkError as ServerParseError\n if (name == 'ServerParseError') {\n message = `[ ${statusCode || ''} : ${response.statusText} ] ${bodyText}`\n } else {\n /* in case this error is instanceof ServerError */\n message = `[ ${statusCode || ''} : ${response.statusText} ] ${message}`\n }\n\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: decodeHTML(message),\n ex: networkError\n }\n })\n )\n }\n }\n}\n\nexport const setClientLink = (link: ApolloLink) => {\n apolloLink = link\n}\n\nexport const getClientLink = () => {\n if (!apolloLink) {\n apolloLink = split(\n ({ query }) => {\n const def = getMainDefinition(query)\n return def.kind === 'OperationDefinition' && def.operation === 'subscription'\n },\n wsLink,\n from([\n activeRequestCounterLink,\n onError(ERROR_HANDLER),\n // Apollo 가 InMemoryCache 정규화를 위해 응답 객체에 자동 삽입한 __typename 이,\n // 호출자가 그 응답을 mutation input 변수로 재전송할 때 strict 검증에서 거부되는\n // 문제를 프레임워크 레벨에서 일괄 제거한다.\n removeTypenameFromVariables(),\n httpLink\n ])\n )\n }\n\n return apolloLink\n}\n\nexport const createMockLink = (mockedResponses: any = {}) =>\n new ApolloLink((operation, forward) => {\n return new Observable(observer => {\n const operationName = operation.operationName\n\n if (mockedResponses[operationName]) {\n observer.next({\n data: mockedResponses[operationName]\n })\n } else {\n observer.error(new Error(`No mock response for operation ${operationName}`))\n }\n\n observer.complete()\n })\n })\n"]}
1
+ {"version":3,"file":"graphql-env.js","sourceRoot":"","sources":["../../src/graphql-env.ts"],"names":[],"mappings":"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","sourcesContent":["import createUploadLink from 'apollo-upload-client/createUploadLink.mjs'\nimport { createClient as createWebSocketClient } from 'graphql-ws'\n\nimport {\n ApolloLink,\n from,\n HttpLink,\n HttpOptions,\n Observable,\n ServerParseError,\n split\n} from '@apollo/client/core/index.js'\nimport { ErrorLink, onError } from '@apollo/client/link/error/index.js'\nimport { errorsShownByScreen, isHandledRefusal } from './handled-refusal.js'\nimport { removeTypenameFromVariables } from '@apollo/client/link/remove-typename/index.js'\nimport { GraphQLWsLink } from '@apollo/client/link/subscriptions/index.js'\nimport { getMainDefinition } from '@apollo/client/utilities/index.js'\n\nimport { decodeHTML } from '@operato/utils'\nimport { activeRequestCounterLink } from './active-request-counter-link.js'\nimport { forbiddenMeaning, sayAddressRefused } from './forbidden.js'\nimport { uploadFetch } from './upload-progress.js'\n\nexport const GRAPHQL_URI = '/graphql'\nexport const SUBSCRIPTION_URI = GRAPHQL_URI\n\nvar apolloLink: ApolloLink\n\nconst httpOptions: HttpOptions = {\n uri: GRAPHQL_URI,\n credentials: 'include',\n headers: { 'Apollo-Require-Preflight': 'true' }\n}\n\nconst httpLink = ApolloLink.split(\n operation => operation.getContext().hasUpload,\n /* uploadFetch: an upload that asks for progress goes through XHR; everything else is plain fetch. */\n createUploadLink({ ...httpOptions, fetch: uploadFetch }) as any,\n new HttpLink(httpOptions)\n)\n\nconst wsLink = new GraphQLWsLink(\n createWebSocketClient({\n url: location.origin.replace(/^http/, 'ws') + SUBSCRIPTION_URI,\n keepAlive: 10_000,\n retryAttempts: 1_000_000,\n shouldRetry: e => true,\n connectionParams: {\n headers: {\n /* \n 특정 도메인의 데이타만 받고자 하는 경우에, referer 정보를 제공해서 서버에서 서브도메인 정보를 취득하도록 한다.\n referer: location.href\n 또는, 이미 서브도메인 정보를 알고 있다면,\n 'x-things-factory-domain': '[subdomain]'\n 을 보낼 수 있다.\n 관련 정보를 보내지 않는다면, 사용자가 권한을 가진 모든 도메인의 데이타를 수신하게 된다.\n */\n referer: location.href\n }\n }\n })\n)\n\nconst ERROR_HANDLER: ErrorLink.ErrorHandler = ({ operation, graphQLErrors, networkError }) => {\n /* A screen that names this refusal says it in its own body (handled-refusal.ts). */\n if (graphQLErrors && !isHandledRefusal(operation.getContext(), graphQLErrors)) {\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: decodeHTML(graphQLErrors[0].message),\n ex: graphQLErrors,\n /*\n * Closed by the snackbar's own ✕. A 'Close' action here did the same thing twice and took the one\n * button slot - a package that knows what an error means (auth-ui, for a refusal a role would open)\n * fills it through registerNotifyAction. This link reports errors; it does not interpret them.\n */\n option: {\n timer: -1 // disable auto close\n }\n }\n })\n )\n }\n\n if (networkError) {\n const shownByScreen = errorsShownByScreen(operation.getContext())\n\n /* A read its page replaced or left is aborted on purpose (PageView.reads, ADR-0190): nothing failed. */\n if (networkError.name === 'AbortError') return\n\n /* networkError가 ServerParseError 이거나 ServerError 인 경우에만 statusCode를 갖는다. */\n switch ((networkError as ServerParseError).statusCode) {\n case undefined /* in case this error is not a server side error */:\n if (shownByScreen) break\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: networkError.message,\n ex: networkError\n }\n })\n )\n break\n\n case 401:\n /* 401 에러가 리턴되면, 인증이 필요하다는 메시지를 dispatch 한다. 이 auth 모듈 등에서 이 메시지를 받아서 signin 프로세스를 진행할 수 있다. */\n document.dispatchEvent(new CustomEvent('auth-required'))\n break\n\n case 403:\n /*\n * 403 은 둘이다 — 도메인이 아직 없는 것과, 이 주소를 사업장이 받지 않는 것(forbidden.ts).\n * 앞의 것은 domain-required 를 보내 auth 가 체크인으로 보낸다. 뒤의 것은 체크인이 풀 수 없으므로\n * 보내지 않고 한 번 말한다 — 보내면 체크인과 화면 사이를 돈다.\n */\n if (forbiddenMeaning((networkError as ServerParseError).response?.headers) === 'address-refused') {\n sayAddressRefused()\n } else {\n document.dispatchEvent(new CustomEvent('domain-required'))\n }\n break\n\n default:\n if (shownByScreen) break\n var { name, response, statusCode, bodyText, message } = networkError as ServerParseError\n if (name == 'ServerParseError') {\n message = `[ ${statusCode || ''} : ${response.statusText} ] ${bodyText}`\n } else {\n /* in case this error is instanceof ServerError */\n message = `[ ${statusCode || ''} : ${response.statusText} ] ${message}`\n }\n\n document.dispatchEvent(\n new CustomEvent('notify', {\n detail: {\n level: 'error',\n message: decodeHTML(message),\n ex: networkError\n }\n })\n )\n }\n }\n}\n\nexport const setClientLink = (link: ApolloLink) => {\n apolloLink = link\n}\n\nexport const getClientLink = () => {\n if (!apolloLink) {\n apolloLink = split(\n ({ query }) => {\n const def = getMainDefinition(query)\n return def.kind === 'OperationDefinition' && def.operation === 'subscription'\n },\n wsLink,\n from([\n activeRequestCounterLink,\n onError(ERROR_HANDLER),\n // Apollo 가 InMemoryCache 정규화를 위해 응답 객체에 자동 삽입한 __typename 이,\n // 호출자가 그 응답을 mutation input 변수로 재전송할 때 strict 검증에서 거부되는\n // 문제를 프레임워크 레벨에서 일괄 제거한다.\n removeTypenameFromVariables(),\n httpLink\n ])\n )\n }\n\n return apolloLink\n}\n\nexport const createMockLink = (mockedResponses: any = {}) =>\n new ApolloLink((operation, forward) => {\n return new Observable(observer => {\n const operationName = operation.operationName\n\n if (mockedResponses[operationName]) {\n observer.next({\n data: mockedResponses[operationName]\n })\n } else {\n observer.error(new Error(`No mock response for operation ${operationName}`))\n }\n\n observer.complete()\n })\n })\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import gql from 'graphql-tag';
3
+ import { client } from '../src/graphql-client.js';
4
+ /**
5
+ * A read its page replaced or left is aborted on purpose (PageView.reads, ADR-0190): the error link says nothing about
6
+ * it. A read that failed on its own is still reported.
7
+ */
8
+ describe('an aborted read', () => {
9
+ const query = gql `
10
+ query {
11
+ ping
12
+ }
13
+ `;
14
+ let notified;
15
+ let fetched;
16
+ const listen = (event) => notified.push(event.detail);
17
+ beforeEach(() => {
18
+ notified = [];
19
+ fetched = window.fetch;
20
+ document.addEventListener('notify', listen);
21
+ });
22
+ afterEach(() => {
23
+ window.fetch = fetched;
24
+ document.removeEventListener('notify', listen);
25
+ });
26
+ it('notifies nothing', async () => {
27
+ window.fetch = (_input, init) => new Promise((_, reject) => init.signal.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError'))));
28
+ const controller = new AbortController();
29
+ const read = client
30
+ .query({ query, fetchPolicy: 'no-cache', context: { fetchOptions: { signal: controller.signal } } })
31
+ .catch(error => error);
32
+ controller.abort();
33
+ await read;
34
+ expect(notified).to.deep.equal([]);
35
+ });
36
+ it('while a read that failed on its own is reported', async () => {
37
+ window.fetch = () => Promise.reject(new TypeError('Failed to fetch'));
38
+ await client.query({ query, fetchPolicy: 'no-cache' }).catch(error => error);
39
+ expect(notified.length).to.equal(1);
40
+ });
41
+ });
42
+ //# sourceMappingURL=aborted-read.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aborted-read.test.js","sourceRoot":"","sources":["../../test/aborted-read.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AAEjD;;;GAGG;AACH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,MAAM,KAAK,GAAG,GAAG,CAAA;;;;GAIhB,CAAA;IACD,IAAI,QAAmB,CAAA;IACvB,IAAI,OAA4B,CAAA;IAChC,MAAM,MAAM,GAAG,CAAC,KAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAE,KAAqB,CAAC,MAAM,CAAC,CAAA;IAE7E,UAAU,CAAC,GAAG,EAAE;QACd,QAAQ,GAAG,EAAE,CAAA;QACb,OAAO,GAAG,MAAM,CAAC,KAAK,CAAA;QACtB,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,KAAK,GAAG,OAAO,CAAA;QACtB,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QAChC,MAAM,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAC9B,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CACxB,IAAK,CAAC,MAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,CACjG,CAAA;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM;aAChB,KAAK,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC;aACnG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QACxB,UAAU,CAAC,KAAK,EAAE,CAAA;QAClB,MAAM,IAAI,CAAA;QACV,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAA;QACrE,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;QAC5E,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect } from '@open-wc/testing'\nimport gql from 'graphql-tag'\n\nimport { client } from '../src/graphql-client.js'\n\n/**\n * A read its page replaced or left is aborted on purpose (PageView.reads, ADR-0190): the error link says nothing about\n * it. A read that failed on its own is still reported.\n */\ndescribe('an aborted read', () => {\n const query = gql`\n query {\n ping\n }\n `\n let notified: unknown[]\n let fetched: typeof window.fetch\n const listen = (event: Event) => notified.push((event as CustomEvent).detail)\n\n beforeEach(() => {\n notified = []\n fetched = window.fetch\n document.addEventListener('notify', listen)\n })\n\n afterEach(() => {\n window.fetch = fetched\n document.removeEventListener('notify', listen)\n })\n\n it('notifies nothing', async () => {\n window.fetch = (_input, init) =>\n new Promise((_, reject) =>\n init!.signal!.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')))\n )\n const controller = new AbortController()\n const read = client\n .query({ query, fetchPolicy: 'no-cache', context: { fetchOptions: { signal: controller.signal } } })\n .catch(error => error)\n controller.abort()\n await read\n expect(notified).to.deep.equal([])\n })\n\n it('while a read that failed on its own is reported', async () => {\n window.fetch = () => Promise.reject(new TypeError('Failed to fetch'))\n await client.query({ query, fetchPolicy: 'no-cache' }).catch(error => error)\n expect(notified.length).to.equal(1)\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import gql from 'graphql-tag';
3
+ import { html, LitElement } from 'lit';
4
+ import { resetActiveRequestCounterCallback, setActiveRequestCounterCallback } from '../src/active-request-counter-link.js';
5
+ import { client } from '../src/graphql-client.js';
6
+ import { QueryController } from '../src/query-controller.js';
7
+ /**
8
+ * The shell shows its progress bar while the count is not zero (shell/actions/busy.ts). Every request that counts up
9
+ * counts down exactly once - answered, failed, refused, aborted, or left by the screen that asked.
10
+ */
11
+ const query = gql `
12
+ query ($id: String) {
13
+ ping(id: $id)
14
+ }
15
+ `;
16
+ const respond = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
17
+ const settle = () => new Promise(resolve => setTimeout(resolve, 20));
18
+ let count;
19
+ let calls;
20
+ let fetched;
21
+ const silence = (event) => event.stopImmediatePropagation();
22
+ beforeEach(() => {
23
+ count = 0;
24
+ calls = [];
25
+ fetched = window.fetch;
26
+ setActiveRequestCounterCallback(now => {
27
+ count = now;
28
+ calls.push(now);
29
+ });
30
+ document.addEventListener('notify', silence);
31
+ document.addEventListener('auth-required', silence);
32
+ document.addEventListener('domain-required', silence);
33
+ });
34
+ afterEach(() => {
35
+ window.fetch = fetched;
36
+ resetActiveRequestCounterCallback();
37
+ document.removeEventListener('notify', silence);
38
+ document.removeEventListener('auth-required', silence);
39
+ document.removeEventListener('domain-required', silence);
40
+ });
41
+ const read = () => client.query({ query, fetchPolicy: 'no-cache' }).catch(error => error);
42
+ describe('the active request count', () => {
43
+ it('goes back to zero after an answer', async () => {
44
+ window.fetch = async () => respond({ data: { ping: 'pong' } });
45
+ await read();
46
+ expect(calls).to.deep.equal([1, 0]);
47
+ });
48
+ it('goes back to zero after an answer with errors', async () => {
49
+ window.fetch = async () => respond({ data: null, errors: [{ message: 'no' }] });
50
+ await read();
51
+ expect(calls).to.deep.equal([1, 0]);
52
+ });
53
+ it('goes back to zero after a dropped connection', async () => {
54
+ window.fetch = () => Promise.reject(new TypeError('Failed to fetch'));
55
+ await read();
56
+ expect(calls).to.deep.equal([1, 0]);
57
+ });
58
+ it('goes back to zero once after an error status that carries data and errors', async () => {
59
+ window.fetch = async () => respond({ data: { ping: null }, errors: [{ message: 'refused' }] }, 403);
60
+ await read();
61
+ await settle();
62
+ expect(calls).to.deep.equal([1, 0]);
63
+ });
64
+ it('goes back to zero after a 401', async () => {
65
+ window.fetch = async () => respond({ errors: [{ message: 'sign in' }] }, 401);
66
+ await read();
67
+ expect(calls).to.deep.equal([1, 0]);
68
+ });
69
+ it('goes back to zero after a read aborted with its signal', async () => {
70
+ window.fetch = (_input, init) => new Promise((_, reject) => init.signal.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError'))));
71
+ const controller = new AbortController();
72
+ const reading = client
73
+ .query({ query, fetchPolicy: 'no-cache', context: { fetchOptions: { signal: controller.signal } } })
74
+ .catch(error => error);
75
+ controller.abort();
76
+ await reading;
77
+ await settle();
78
+ expect(count).to.equal(0);
79
+ });
80
+ it('goes back to zero when the watcher stops before the answer', async () => {
81
+ let answer;
82
+ window.fetch = () => new Promise(resolve => (answer = resolve));
83
+ const subscription = client.watchQuery({ query, fetchPolicy: 'no-cache' }).subscribe({ error: () => { } });
84
+ await settle();
85
+ expect(count).to.equal(1);
86
+ subscription.unsubscribe();
87
+ await settle();
88
+ expect(count).to.equal(0);
89
+ /* The answer that comes after is not counted again. */
90
+ answer(respond({ data: { ping: 'late' } }));
91
+ await settle();
92
+ expect(calls).to.deep.equal([1, 0]);
93
+ });
94
+ it('goes back to zero when a screen asks for other variables, and when it leaves', async () => {
95
+ const pending = [];
96
+ window.fetch = () => new Promise(resolve => pending.push(resolve));
97
+ class PingView extends LitElement {
98
+ constructor() {
99
+ super(...arguments);
100
+ this.pingId = 'a';
101
+ this.ping = new QueryController(this, query, { variables: () => ({ id: this.pingId }) });
102
+ }
103
+ render() {
104
+ return html ``;
105
+ }
106
+ }
107
+ PingView.properties = { pingId: {} };
108
+ customElements.define('active-request-ping-view', PingView);
109
+ const view = await fixture(html `<active-request-ping-view></active-request-ping-view>`);
110
+ await settle();
111
+ expect(count).to.equal(1);
112
+ view.pingId = 'b';
113
+ await view.updateComplete;
114
+ await settle();
115
+ expect(count).to.equal(1);
116
+ view.remove();
117
+ await settle();
118
+ expect(count).to.equal(0);
119
+ pending.forEach(resolve => resolve(respond({ data: { ping: 'late' } })));
120
+ await settle();
121
+ expect(count).to.equal(0);
122
+ });
123
+ });
124
+ //# sourceMappingURL=active-request-counter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-request-counter.test.js","sourceRoot":"","sources":["../../test/active-request-counter.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport gql from 'graphql-tag'\nimport { html, LitElement } from 'lit'\n\nimport { resetActiveRequestCounterCallback, setActiveRequestCounterCallback } from '../src/active-request-counter-link.js'\nimport { client } from '../src/graphql-client.js'\nimport { QueryController } from '../src/query-controller.js'\n\n/**\n * The shell shows its progress bar while the count is not zero (shell/actions/busy.ts). Every request that counts up\n * counts down exactly once - answered, failed, refused, aborted, or left by the screen that asked.\n */\n\nconst query = gql`\n query ($id: String) {\n ping(id: $id)\n }\n`\n\nconst respond = (body: unknown, status = 200) =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } })\n\nconst settle = () => new Promise(resolve => setTimeout(resolve, 20))\n\nlet count: number\nlet calls: number[]\nlet fetched: typeof window.fetch\nconst silence = (event: Event) => event.stopImmediatePropagation()\n\nbeforeEach(() => {\n count = 0\n calls = []\n fetched = window.fetch\n setActiveRequestCounterCallback(now => {\n count = now\n calls.push(now)\n })\n document.addEventListener('notify', silence)\n document.addEventListener('auth-required', silence)\n document.addEventListener('domain-required', silence)\n})\n\nafterEach(() => {\n window.fetch = fetched\n resetActiveRequestCounterCallback()\n document.removeEventListener('notify', silence)\n document.removeEventListener('auth-required', silence)\n document.removeEventListener('domain-required', silence)\n})\n\nconst read = () => client.query({ query, fetchPolicy: 'no-cache' }).catch(error => error)\n\ndescribe('the active request count', () => {\n it('goes back to zero after an answer', async () => {\n window.fetch = async () => respond({ data: { ping: 'pong' } })\n await read()\n expect(calls).to.deep.equal([1, 0])\n })\n\n it('goes back to zero after an answer with errors', async () => {\n window.fetch = async () => respond({ data: null, errors: [{ message: 'no' }] })\n await read()\n expect(calls).to.deep.equal([1, 0])\n })\n\n it('goes back to zero after a dropped connection', async () => {\n window.fetch = () => Promise.reject(new TypeError('Failed to fetch'))\n await read()\n expect(calls).to.deep.equal([1, 0])\n })\n\n it('goes back to zero once after an error status that carries data and errors', async () => {\n window.fetch = async () => respond({ data: { ping: null }, errors: [{ message: 'refused' }] }, 403)\n await read()\n await settle()\n expect(calls).to.deep.equal([1, 0])\n })\n\n it('goes back to zero after a 401', async () => {\n window.fetch = async () => respond({ errors: [{ message: 'sign in' }] }, 401)\n await read()\n expect(calls).to.deep.equal([1, 0])\n })\n\n it('goes back to zero after a read aborted with its signal', async () => {\n window.fetch = (_input, init) =>\n new Promise((_, reject) =>\n init!.signal!.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')))\n )\n const controller = new AbortController()\n const reading = client\n .query({ query, fetchPolicy: 'no-cache', context: { fetchOptions: { signal: controller.signal } } })\n .catch(error => error)\n controller.abort()\n await reading\n await settle()\n expect(count).to.equal(0)\n })\n\n it('goes back to zero when the watcher stops before the answer', async () => {\n let answer!: (response: Response) => void\n window.fetch = () => new Promise(resolve => (answer = resolve))\n const subscription = client.watchQuery({ query, fetchPolicy: 'no-cache' }).subscribe({ error: () => {} })\n await settle()\n expect(count).to.equal(1)\n subscription.unsubscribe()\n await settle()\n expect(count).to.equal(0)\n /* The answer that comes after is not counted again. */\n answer(respond({ data: { ping: 'late' } }))\n await settle()\n expect(calls).to.deep.equal([1, 0])\n })\n\n it('goes back to zero when a screen asks for other variables, and when it leaves', async () => {\n const pending: ((response: Response) => void)[] = []\n window.fetch = () => new Promise(resolve => pending.push(resolve))\n\n class PingView extends LitElement {\n static properties = { pingId: {} }\n pingId = 'a'\n ping = new QueryController(this, query, { variables: () => ({ id: this.pingId }) })\n render() {\n return html``\n }\n }\n customElements.define('active-request-ping-view', PingView)\n\n const view = await fixture<PingView>(html`<active-request-ping-view></active-request-ping-view>`)\n await settle()\n expect(count).to.equal(1)\n\n view.pingId = 'b'\n await view.updateComplete\n await settle()\n expect(count).to.equal(1)\n\n view.remove()\n await settle()\n expect(count).to.equal(0)\n\n pending.forEach(resolve => resolve(respond({ data: { ping: 'late' } })))\n await settle()\n expect(count).to.equal(0)\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { ADDRESS_REFUSED, forbiddenMeaning, REFUSAL_HEADER, resetAddressRefusalSaid, sayAddressRefused } from '../src/forbidden.js';
3
+ /**
4
+ * A 403 that says the address was refused is not sent to check-in - check-in cannot let the address
5
+ * in, and the screen would go round between the two. It is said once, however many queries got it.
6
+ */
7
+ describe('what a 403 means', () => {
8
+ it('★ the refusal header means the address was refused', () => {
9
+ expect(forbiddenMeaning(new Headers({ [REFUSAL_HEADER]: ADDRESS_REFUSED }))).to.equal('address-refused');
10
+ });
11
+ it('★ anything else is still "domain required" - the check-in path stays as it was', () => {
12
+ expect(forbiddenMeaning(new Headers())).to.equal('domain-required');
13
+ expect(forbiddenMeaning(new Headers({ [REFUSAL_HEADER]: 'something-else' }))).to.equal('domain-required');
14
+ expect(forbiddenMeaning(undefined)).to.equal('domain-required');
15
+ });
16
+ });
17
+ describe('saying the address was refused', () => {
18
+ beforeEach(() => resetAddressRefusalSaid());
19
+ it('★ is said once, as an error, however many queries were refused', () => {
20
+ const target = new EventTarget();
21
+ const said = [];
22
+ target.addEventListener('notify', event => said.push(event.detail));
23
+ expect(sayAddressRefused(target)).to.equal(true);
24
+ expect(sayAddressRefused(target)).to.equal(false);
25
+ expect(sayAddressRefused(target)).to.equal(false);
26
+ expect(said.length).to.equal(1);
27
+ expect(said[0].level).to.equal('error');
28
+ expect(said[0].message).to.be.a('string').and.not.equal('');
29
+ });
30
+ });
31
+ //# sourceMappingURL=forbidden.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forbidden.test.js","sourceRoot":"","sources":["../../test/forbidden.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAEnI;;;GAGG;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;IAC1G,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,CAAC,gBAAgB,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;QACnE,MAAM,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;QACzG,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;IACjE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,UAAU,CAAC,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAC,CAAA;IAE3C,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,IAAI,GAAU,EAAE,CAAA;QACtB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAE,KAAqB,CAAC,MAAM,CAAC,CAAC,CAAA;QAEpF,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAEjD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC/B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { ADDRESS_REFUSED, forbiddenMeaning, REFUSAL_HEADER, resetAddressRefusalSaid, sayAddressRefused } from '../src/forbidden.js'\n\n/**\n * A 403 that says the address was refused is not sent to check-in - check-in cannot let the address\n * in, and the screen would go round between the two. It is said once, however many queries got it.\n */\n\ndescribe('what a 403 means', () => {\n it('★ the refusal header means the address was refused', () => {\n expect(forbiddenMeaning(new Headers({ [REFUSAL_HEADER]: ADDRESS_REFUSED }))).to.equal('address-refused')\n })\n\n it('★ anything else is still \"domain required\" - the check-in path stays as it was', () => {\n expect(forbiddenMeaning(new Headers())).to.equal('domain-required')\n expect(forbiddenMeaning(new Headers({ [REFUSAL_HEADER]: 'something-else' }))).to.equal('domain-required')\n expect(forbiddenMeaning(undefined)).to.equal('domain-required')\n })\n})\n\ndescribe('saying the address was refused', () => {\n beforeEach(() => resetAddressRefusalSaid())\n\n it('★ is said once, as an error, however many queries were refused', () => {\n const target = new EventTarget()\n const said: any[] = []\n target.addEventListener('notify', event => said.push((event as CustomEvent).detail))\n\n expect(sayAddressRefused(target)).to.equal(true)\n expect(sayAddressRefused(target)).to.equal(false)\n expect(sayAddressRefused(target)).to.equal(false)\n\n expect(said.length).to.equal(1)\n expect(said[0].level).to.equal('error')\n expect(said[0].message).to.be.a('string').and.not.equal('')\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { isHandledRefusal } from '../src/handled-refusal.js';
3
+ const refusal = (code) => ({ message: 'refused', extensions: { code: 'BAD_USER_INPUT', refusal: { code, params: {} } } });
4
+ describe('isHandledRefusal', () => {
5
+ it('a refusal the query names is handled by the screen', () => {
6
+ expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [refusal('twin-model-no-production-spec')])).to.equal(true);
7
+ });
8
+ it('a refusal the query does not name is still reported', () => {
9
+ expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [refusal('other')])).to.equal(false);
10
+ });
11
+ it('an error without a refusal code is still reported', () => {
12
+ expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [{ message: 'boom', extensions: { code: 'INTERNAL_SERVER_ERROR' } }])).to.equal(false);
13
+ });
14
+ it('one unnamed error among named refusals is still reported', () => {
15
+ expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [refusal('twin-model-no-production-spec'), { message: 'boom' }])).to.equal(false);
16
+ });
17
+ it('a query that names nothing has every error reported', () => {
18
+ expect(isHandledRefusal({}, [refusal('twin-model-no-production-spec')])).to.equal(false);
19
+ expect(isHandledRefusal(undefined, [refusal('twin-model-no-production-spec')])).to.equal(false);
20
+ });
21
+ });
22
+ //# sourceMappingURL=handled-refusal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handled-refusal.test.js","sourceRoot":"","sources":["../../test/handled-refusal.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAE5D,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAEjI,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC,+BAA+B,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC7I,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC,+BAA+B,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACtH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC,+BAA+B,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACxK,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CACJ,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC,+BAA+B,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAC1I,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACnB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACxF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACjG,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { isHandledRefusal } from '../src/handled-refusal.js'\n\nconst refusal = (code: string) => ({ message: 'refused', extensions: { code: 'BAD_USER_INPUT', refusal: { code, params: {} } } })\n\ndescribe('isHandledRefusal', () => {\n it('a refusal the query names is handled by the screen', () => {\n expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [refusal('twin-model-no-production-spec')])).to.equal(true)\n })\n\n it('a refusal the query does not name is still reported', () => {\n expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [refusal('other')])).to.equal(false)\n })\n\n it('an error without a refusal code is still reported', () => {\n expect(isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [{ message: 'boom', extensions: { code: 'INTERNAL_SERVER_ERROR' } }])).to.equal(false)\n })\n\n it('one unnamed error among named refusals is still reported', () => {\n expect(\n isHandledRefusal({ handledRefusals: ['twin-model-no-production-spec'] }, [refusal('twin-model-no-production-spec'), { message: 'boom' }])\n ).to.equal(false)\n })\n\n it('a query that names nothing has every error reported', () => {\n expect(isHandledRefusal({}, [refusal('twin-model-no-production-spec')])).to.equal(false)\n expect(isHandledRefusal(undefined, [refusal('twin-model-no-production-spec')])).to.equal(false)\n })\n})\n"]}
@@ -0,0 +1 @@
1
+ export {};