فهرست منبع

Always optional metadata fields, more tests, standarized metadata handling

Leszek Wiesner 3 سال پیش
والد
کامیت
2ac4cd9629
21فایلهای تغییر یافته به همراه478 افزوده شده و 402 حذف شده
  1. 17 17
      metadata-protobuf/compiled/index.d.ts
  2. 84 87
      metadata-protobuf/compiled/index.js
  3. 13 13
      metadata-protobuf/proto/WorkingGroups.proto
  4. 4 4
      query-node/mappings/membership.ts
  5. 30 48
      query-node/mappings/workingGroups.ts
  6. 8 8
      query-node/schemas/workingGroups.graphql
  7. 2 2
      tests/integration-tests/src/fixtures/membership/BuyMembershipHappyCaseFixture.ts
  8. 22 104
      tests/integration-tests/src/fixtures/workingGroups/BaseCreateOpeningFixture.ts
  9. 61 9
      tests/integration-tests/src/fixtures/workingGroups/CreateOpeningsFixture.ts
  10. 68 41
      tests/integration-tests/src/fixtures/workingGroups/CreateUpcomingOpeningsFixture.ts
  11. 2 2
      tests/integration-tests/src/fixtures/workingGroups/HireWorkersFixture.ts
  12. 7 5
      tests/integration-tests/src/fixtures/workingGroups/UpdateGroupStatusFixture.ts
  13. 6 2
      tests/integration-tests/src/fixtures/workingGroups/index.ts
  14. 1 1
      tests/integration-tests/src/flows/working-groups/groupStatus.ts
  15. 2 1
      tests/integration-tests/src/flows/working-groups/leadOpening.ts
  16. 55 8
      tests/integration-tests/src/flows/working-groups/openingsAndApplications.ts
  17. 74 28
      tests/integration-tests/src/flows/working-groups/upcomingOpenings.ts
  18. 6 6
      tests/integration-tests/src/graphql/generated/queries.ts
  19. 12 12
      tests/integration-tests/src/graphql/generated/schema.ts
  20. 2 2
      tests/integration-tests/src/scenarios/workingGroups.ts
  21. 2 2
      tests/integration-tests/src/utils.ts

+ 17 - 17
metadata-protobuf/compiled/index.d.ts

@@ -213,10 +213,10 @@ export class MembershipMetadata implements IMembershipMetadata {
 export interface IOpeningMetadata {
 
     /** OpeningMetadata shortDescription */
-    shortDescription: string;
+    shortDescription?: (string|null);
 
     /** OpeningMetadata description */
-    description: string;
+    description?: (string|null);
 
     /** OpeningMetadata hiringLimit */
     hiringLimit?: (number|null);
@@ -225,7 +225,7 @@ export interface IOpeningMetadata {
     expectedEndingTimestamp?: (number|null);
 
     /** OpeningMetadata applicationDetails */
-    applicationDetails: string;
+    applicationDetails?: (string|null);
 
     /** OpeningMetadata applicationFormQuestions */
     applicationFormQuestions?: (OpeningMetadata.IApplicationFormQuestion[]|null);
@@ -335,10 +335,10 @@ export namespace OpeningMetadata {
     interface IApplicationFormQuestion {
 
         /** ApplicationFormQuestion question */
-        question: string;
+        question?: (string|null);
 
         /** ApplicationFormQuestion type */
-        type: OpeningMetadata.ApplicationFormQuestion.InputType;
+        type?: (OpeningMetadata.ApplicationFormQuestion.InputType|null);
     }
 
     /** Represents an ApplicationFormQuestion. */
@@ -431,8 +431,8 @@ export namespace OpeningMetadata {
 
         /** InputType enum. */
         enum InputType {
-            TEXT = 1,
-            TEXTAREA = 2
+            TEXT = 0,
+            TEXTAREA = 1
         }
     }
 }
@@ -441,16 +441,16 @@ export namespace OpeningMetadata {
 export interface IUpcomingOpeningMetadata {
 
     /** UpcomingOpeningMetadata expectedStart */
-    expectedStart: number;
+    expectedStart?: (number|null);
 
     /** UpcomingOpeningMetadata rewardPerBlock */
-    rewardPerBlock: Long;
+    rewardPerBlock?: (Long|null);
 
     /** UpcomingOpeningMetadata minApplicationStake */
-    minApplicationStake: Long;
+    minApplicationStake?: (Long|null);
 
     /** UpcomingOpeningMetadata metadata */
-    metadata: IOpeningMetadata;
+    metadata?: (IOpeningMetadata|null);
 }
 
 /** Represents an UpcomingOpeningMetadata. */
@@ -472,7 +472,7 @@ export class UpcomingOpeningMetadata implements IUpcomingOpeningMetadata {
     public minApplicationStake: Long;
 
     /** UpcomingOpeningMetadata metadata. */
-    public metadata: IOpeningMetadata;
+    public metadata?: (IOpeningMetadata|null);
 
     /**
      * Creates a new UpcomingOpeningMetadata instance using the specified properties.
@@ -747,7 +747,7 @@ export class WorkingGroupMetadata implements IWorkingGroupMetadata {
 export interface ISetGroupMetadata {
 
     /** SetGroupMetadata newMetadata */
-    newMetadata: IWorkingGroupMetadata;
+    newMetadata?: (IWorkingGroupMetadata|null);
 }
 
 /** Represents a SetGroupMetadata. */
@@ -760,7 +760,7 @@ export class SetGroupMetadata implements ISetGroupMetadata {
     constructor(properties?: ISetGroupMetadata);
 
     /** SetGroupMetadata newMetadata. */
-    public newMetadata: IWorkingGroupMetadata;
+    public newMetadata?: (IWorkingGroupMetadata|null);
 
     /**
      * Creates a new SetGroupMetadata instance using the specified properties.
@@ -837,7 +837,7 @@ export class SetGroupMetadata implements ISetGroupMetadata {
 export interface IAddUpcomingOpening {
 
     /** AddUpcomingOpening metadata */
-    metadata: IUpcomingOpeningMetadata;
+    metadata?: (IUpcomingOpeningMetadata|null);
 }
 
 /** Represents an AddUpcomingOpening. */
@@ -850,7 +850,7 @@ export class AddUpcomingOpening implements IAddUpcomingOpening {
     constructor(properties?: IAddUpcomingOpening);
 
     /** AddUpcomingOpening metadata. */
-    public metadata: IUpcomingOpeningMetadata;
+    public metadata?: (IUpcomingOpeningMetadata|null);
 
     /**
      * Creates a new AddUpcomingOpening instance using the specified properties.
@@ -927,7 +927,7 @@ export class AddUpcomingOpening implements IAddUpcomingOpening {
 export interface IRemoveUpcomingOpening {
 
     /** RemoveUpcomingOpening id */
-    id: string;
+    id?: (string|null);
 }
 
 /** Represents a RemoveUpcomingOpening. */

+ 84 - 87
metadata-protobuf/compiled/index.js

@@ -518,11 +518,11 @@ $root.OpeningMetadata = (function() {
      * Properties of an OpeningMetadata.
      * @exports IOpeningMetadata
      * @interface IOpeningMetadata
-     * @property {string} shortDescription OpeningMetadata shortDescription
-     * @property {string} description OpeningMetadata description
+     * @property {string|null} [shortDescription] OpeningMetadata shortDescription
+     * @property {string|null} [description] OpeningMetadata description
      * @property {number|null} [hiringLimit] OpeningMetadata hiringLimit
      * @property {number|null} [expectedEndingTimestamp] OpeningMetadata expectedEndingTimestamp
-     * @property {string} applicationDetails OpeningMetadata applicationDetails
+     * @property {string|null} [applicationDetails] OpeningMetadata applicationDetails
      * @property {Array.<OpeningMetadata.IApplicationFormQuestion>|null} [applicationFormQuestions] OpeningMetadata applicationFormQuestions
      */
 
@@ -614,13 +614,16 @@ $root.OpeningMetadata = (function() {
     OpeningMetadata.encode = function encode(message, writer) {
         if (!writer)
             writer = $Writer.create();
-        writer.uint32(/* id 1, wireType 2 =*/10).string(message.shortDescription);
-        writer.uint32(/* id 2, wireType 2 =*/18).string(message.description);
+        if (message.shortDescription != null && Object.hasOwnProperty.call(message, "shortDescription"))
+            writer.uint32(/* id 1, wireType 2 =*/10).string(message.shortDescription);
+        if (message.description != null && Object.hasOwnProperty.call(message, "description"))
+            writer.uint32(/* id 2, wireType 2 =*/18).string(message.description);
         if (message.hiringLimit != null && Object.hasOwnProperty.call(message, "hiringLimit"))
             writer.uint32(/* id 3, wireType 0 =*/24).uint32(message.hiringLimit);
         if (message.expectedEndingTimestamp != null && Object.hasOwnProperty.call(message, "expectedEndingTimestamp"))
             writer.uint32(/* id 4, wireType 0 =*/32).uint32(message.expectedEndingTimestamp);
-        writer.uint32(/* id 5, wireType 2 =*/42).string(message.applicationDetails);
+        if (message.applicationDetails != null && Object.hasOwnProperty.call(message, "applicationDetails"))
+            writer.uint32(/* id 5, wireType 2 =*/42).string(message.applicationDetails);
         if (message.applicationFormQuestions != null && message.applicationFormQuestions.length)
             for (var i = 0; i < message.applicationFormQuestions.length; ++i)
                 $root.OpeningMetadata.ApplicationFormQuestion.encode(message.applicationFormQuestions[i], writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim();
@@ -683,12 +686,6 @@ $root.OpeningMetadata = (function() {
                 break;
             }
         }
-        if (!message.hasOwnProperty("shortDescription"))
-            throw $util.ProtocolError("missing required 'shortDescription'", { instance: message });
-        if (!message.hasOwnProperty("description"))
-            throw $util.ProtocolError("missing required 'description'", { instance: message });
-        if (!message.hasOwnProperty("applicationDetails"))
-            throw $util.ProtocolError("missing required 'applicationDetails'", { instance: message });
         return message;
     };
 
@@ -719,18 +716,21 @@ $root.OpeningMetadata = (function() {
     OpeningMetadata.verify = function verify(message) {
         if (typeof message !== "object" || message === null)
             return "object expected";
-        if (!$util.isString(message.shortDescription))
-            return "shortDescription: string expected";
-        if (!$util.isString(message.description))
-            return "description: string expected";
+        if (message.shortDescription != null && message.hasOwnProperty("shortDescription"))
+            if (!$util.isString(message.shortDescription))
+                return "shortDescription: string expected";
+        if (message.description != null && message.hasOwnProperty("description"))
+            if (!$util.isString(message.description))
+                return "description: string expected";
         if (message.hiringLimit != null && message.hasOwnProperty("hiringLimit"))
             if (!$util.isInteger(message.hiringLimit))
                 return "hiringLimit: integer expected";
         if (message.expectedEndingTimestamp != null && message.hasOwnProperty("expectedEndingTimestamp"))
             if (!$util.isInteger(message.expectedEndingTimestamp))
                 return "expectedEndingTimestamp: integer expected";
-        if (!$util.isString(message.applicationDetails))
-            return "applicationDetails: string expected";
+        if (message.applicationDetails != null && message.hasOwnProperty("applicationDetails"))
+            if (!$util.isString(message.applicationDetails))
+                return "applicationDetails: string expected";
         if (message.applicationFormQuestions != null && message.hasOwnProperty("applicationFormQuestions")) {
             if (!Array.isArray(message.applicationFormQuestions))
                 return "applicationFormQuestions: array expected";
@@ -835,8 +835,8 @@ $root.OpeningMetadata = (function() {
          * Properties of an ApplicationFormQuestion.
          * @memberof OpeningMetadata
          * @interface IApplicationFormQuestion
-         * @property {string} question ApplicationFormQuestion question
-         * @property {OpeningMetadata.ApplicationFormQuestion.InputType} type ApplicationFormQuestion type
+         * @property {string|null} [question] ApplicationFormQuestion question
+         * @property {OpeningMetadata.ApplicationFormQuestion.InputType|null} [type] ApplicationFormQuestion type
          */
 
         /**
@@ -868,7 +868,7 @@ $root.OpeningMetadata = (function() {
          * @memberof OpeningMetadata.ApplicationFormQuestion
          * @instance
          */
-        ApplicationFormQuestion.prototype.type = 1;
+        ApplicationFormQuestion.prototype.type = 0;
 
         /**
          * Creates a new ApplicationFormQuestion instance using the specified properties.
@@ -894,8 +894,10 @@ $root.OpeningMetadata = (function() {
         ApplicationFormQuestion.encode = function encode(message, writer) {
             if (!writer)
                 writer = $Writer.create();
-            writer.uint32(/* id 1, wireType 2 =*/10).string(message.question);
-            writer.uint32(/* id 2, wireType 0 =*/16).int32(message.type);
+            if (message.question != null && Object.hasOwnProperty.call(message, "question"))
+                writer.uint32(/* id 1, wireType 2 =*/10).string(message.question);
+            if (message.type != null && Object.hasOwnProperty.call(message, "type"))
+                writer.uint32(/* id 2, wireType 0 =*/16).int32(message.type);
             return writer;
         };
 
@@ -941,10 +943,6 @@ $root.OpeningMetadata = (function() {
                     break;
                 }
             }
-            if (!message.hasOwnProperty("question"))
-                throw $util.ProtocolError("missing required 'question'", { instance: message });
-            if (!message.hasOwnProperty("type"))
-                throw $util.ProtocolError("missing required 'type'", { instance: message });
             return message;
         };
 
@@ -975,15 +973,17 @@ $root.OpeningMetadata = (function() {
         ApplicationFormQuestion.verify = function verify(message) {
             if (typeof message !== "object" || message === null)
                 return "object expected";
-            if (!$util.isString(message.question))
-                return "question: string expected";
-            switch (message.type) {
-            default:
-                return "type: enum value expected";
-            case 1:
-            case 2:
-                break;
-            }
+            if (message.question != null && message.hasOwnProperty("question"))
+                if (!$util.isString(message.question))
+                    return "question: string expected";
+            if (message.type != null && message.hasOwnProperty("type"))
+                switch (message.type) {
+                default:
+                    return "type: enum value expected";
+                case 0:
+                case 1:
+                    break;
+                }
             return null;
         };
 
@@ -1003,12 +1003,12 @@ $root.OpeningMetadata = (function() {
                 message.question = String(object.question);
             switch (object.type) {
             case "TEXT":
-            case 1:
-                message.type = 1;
+            case 0:
+                message.type = 0;
                 break;
             case "TEXTAREA":
-            case 2:
-                message.type = 2;
+            case 1:
+                message.type = 1;
                 break;
             }
             return message;
@@ -1029,7 +1029,7 @@ $root.OpeningMetadata = (function() {
             var object = {};
             if (options.defaults) {
                 object.question = "";
-                object.type = options.enums === String ? "TEXT" : 1;
+                object.type = options.enums === String ? "TEXT" : 0;
             }
             if (message.question != null && message.hasOwnProperty("question"))
                 object.question = message.question;
@@ -1053,13 +1053,13 @@ $root.OpeningMetadata = (function() {
          * InputType enum.
          * @name OpeningMetadata.ApplicationFormQuestion.InputType
          * @enum {number}
-         * @property {number} TEXT=1 TEXT value
-         * @property {number} TEXTAREA=2 TEXTAREA value
+         * @property {number} TEXT=0 TEXT value
+         * @property {number} TEXTAREA=1 TEXTAREA value
          */
         ApplicationFormQuestion.InputType = (function() {
             var valuesById = {}, values = Object.create(valuesById);
-            values[valuesById[1] = "TEXT"] = 1;
-            values[valuesById[2] = "TEXTAREA"] = 2;
+            values[valuesById[0] = "TEXT"] = 0;
+            values[valuesById[1] = "TEXTAREA"] = 1;
             return values;
         })();
 
@@ -1075,10 +1075,10 @@ $root.UpcomingOpeningMetadata = (function() {
      * Properties of an UpcomingOpeningMetadata.
      * @exports IUpcomingOpeningMetadata
      * @interface IUpcomingOpeningMetadata
-     * @property {number} expectedStart UpcomingOpeningMetadata expectedStart
-     * @property {Long} rewardPerBlock UpcomingOpeningMetadata rewardPerBlock
-     * @property {Long} minApplicationStake UpcomingOpeningMetadata minApplicationStake
-     * @property {IOpeningMetadata} metadata UpcomingOpeningMetadata metadata
+     * @property {number|null} [expectedStart] UpcomingOpeningMetadata expectedStart
+     * @property {Long|null} [rewardPerBlock] UpcomingOpeningMetadata rewardPerBlock
+     * @property {Long|null} [minApplicationStake] UpcomingOpeningMetadata minApplicationStake
+     * @property {IOpeningMetadata|null} [metadata] UpcomingOpeningMetadata metadata
      */
 
     /**
@@ -1122,7 +1122,7 @@ $root.UpcomingOpeningMetadata = (function() {
 
     /**
      * UpcomingOpeningMetadata metadata.
-     * @member {IOpeningMetadata} metadata
+     * @member {IOpeningMetadata|null|undefined} metadata
      * @memberof UpcomingOpeningMetadata
      * @instance
      */
@@ -1152,10 +1152,14 @@ $root.UpcomingOpeningMetadata = (function() {
     UpcomingOpeningMetadata.encode = function encode(message, writer) {
         if (!writer)
             writer = $Writer.create();
-        writer.uint32(/* id 1, wireType 0 =*/8).uint32(message.expectedStart);
-        writer.uint32(/* id 2, wireType 0 =*/16).uint64(message.rewardPerBlock);
-        writer.uint32(/* id 3, wireType 0 =*/24).uint64(message.minApplicationStake);
-        $root.OpeningMetadata.encode(message.metadata, writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
+        if (message.expectedStart != null && Object.hasOwnProperty.call(message, "expectedStart"))
+            writer.uint32(/* id 1, wireType 0 =*/8).uint32(message.expectedStart);
+        if (message.rewardPerBlock != null && Object.hasOwnProperty.call(message, "rewardPerBlock"))
+            writer.uint32(/* id 2, wireType 0 =*/16).uint64(message.rewardPerBlock);
+        if (message.minApplicationStake != null && Object.hasOwnProperty.call(message, "minApplicationStake"))
+            writer.uint32(/* id 3, wireType 0 =*/24).uint64(message.minApplicationStake);
+        if (message.metadata != null && Object.hasOwnProperty.call(message, "metadata"))
+            $root.OpeningMetadata.encode(message.metadata, writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
         return writer;
     };
 
@@ -1207,14 +1211,6 @@ $root.UpcomingOpeningMetadata = (function() {
                 break;
             }
         }
-        if (!message.hasOwnProperty("expectedStart"))
-            throw $util.ProtocolError("missing required 'expectedStart'", { instance: message });
-        if (!message.hasOwnProperty("rewardPerBlock"))
-            throw $util.ProtocolError("missing required 'rewardPerBlock'", { instance: message });
-        if (!message.hasOwnProperty("minApplicationStake"))
-            throw $util.ProtocolError("missing required 'minApplicationStake'", { instance: message });
-        if (!message.hasOwnProperty("metadata"))
-            throw $util.ProtocolError("missing required 'metadata'", { instance: message });
         return message;
     };
 
@@ -1245,13 +1241,16 @@ $root.UpcomingOpeningMetadata = (function() {
     UpcomingOpeningMetadata.verify = function verify(message) {
         if (typeof message !== "object" || message === null)
             return "object expected";
-        if (!$util.isInteger(message.expectedStart))
-            return "expectedStart: integer expected";
-        if (!$util.isInteger(message.rewardPerBlock) && !(message.rewardPerBlock && $util.isInteger(message.rewardPerBlock.low) && $util.isInteger(message.rewardPerBlock.high)))
-            return "rewardPerBlock: integer|Long expected";
-        if (!$util.isInteger(message.minApplicationStake) && !(message.minApplicationStake && $util.isInteger(message.minApplicationStake.low) && $util.isInteger(message.minApplicationStake.high)))
-            return "minApplicationStake: integer|Long expected";
-        {
+        if (message.expectedStart != null && message.hasOwnProperty("expectedStart"))
+            if (!$util.isInteger(message.expectedStart))
+                return "expectedStart: integer expected";
+        if (message.rewardPerBlock != null && message.hasOwnProperty("rewardPerBlock"))
+            if (!$util.isInteger(message.rewardPerBlock) && !(message.rewardPerBlock && $util.isInteger(message.rewardPerBlock.low) && $util.isInteger(message.rewardPerBlock.high)))
+                return "rewardPerBlock: integer|Long expected";
+        if (message.minApplicationStake != null && message.hasOwnProperty("minApplicationStake"))
+            if (!$util.isInteger(message.minApplicationStake) && !(message.minApplicationStake && $util.isInteger(message.minApplicationStake.low) && $util.isInteger(message.minApplicationStake.high)))
+                return "minApplicationStake: integer|Long expected";
+        if (message.metadata != null && message.hasOwnProperty("metadata")) {
             var error = $root.OpeningMetadata.verify(message.metadata);
             if (error)
                 return "metadata." + error;
@@ -1820,7 +1819,7 @@ $root.SetGroupMetadata = (function() {
      * Properties of a SetGroupMetadata.
      * @exports ISetGroupMetadata
      * @interface ISetGroupMetadata
-     * @property {IWorkingGroupMetadata} newMetadata SetGroupMetadata newMetadata
+     * @property {IWorkingGroupMetadata|null} [newMetadata] SetGroupMetadata newMetadata
      */
 
     /**
@@ -1840,7 +1839,7 @@ $root.SetGroupMetadata = (function() {
 
     /**
      * SetGroupMetadata newMetadata.
-     * @member {IWorkingGroupMetadata} newMetadata
+     * @member {IWorkingGroupMetadata|null|undefined} newMetadata
      * @memberof SetGroupMetadata
      * @instance
      */
@@ -1870,7 +1869,8 @@ $root.SetGroupMetadata = (function() {
     SetGroupMetadata.encode = function encode(message, writer) {
         if (!writer)
             writer = $Writer.create();
-        $root.WorkingGroupMetadata.encode(message.newMetadata, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim();
+        if (message.newMetadata != null && Object.hasOwnProperty.call(message, "newMetadata"))
+            $root.WorkingGroupMetadata.encode(message.newMetadata, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim();
         return writer;
     };
 
@@ -1913,8 +1913,6 @@ $root.SetGroupMetadata = (function() {
                 break;
             }
         }
-        if (!message.hasOwnProperty("newMetadata"))
-            throw $util.ProtocolError("missing required 'newMetadata'", { instance: message });
         return message;
     };
 
@@ -1945,7 +1943,7 @@ $root.SetGroupMetadata = (function() {
     SetGroupMetadata.verify = function verify(message) {
         if (typeof message !== "object" || message === null)
             return "object expected";
-        {
+        if (message.newMetadata != null && message.hasOwnProperty("newMetadata")) {
             var error = $root.WorkingGroupMetadata.verify(message.newMetadata);
             if (error)
                 return "newMetadata." + error;
@@ -2013,7 +2011,7 @@ $root.AddUpcomingOpening = (function() {
      * Properties of an AddUpcomingOpening.
      * @exports IAddUpcomingOpening
      * @interface IAddUpcomingOpening
-     * @property {IUpcomingOpeningMetadata} metadata AddUpcomingOpening metadata
+     * @property {IUpcomingOpeningMetadata|null} [metadata] AddUpcomingOpening metadata
      */
 
     /**
@@ -2033,7 +2031,7 @@ $root.AddUpcomingOpening = (function() {
 
     /**
      * AddUpcomingOpening metadata.
-     * @member {IUpcomingOpeningMetadata} metadata
+     * @member {IUpcomingOpeningMetadata|null|undefined} metadata
      * @memberof AddUpcomingOpening
      * @instance
      */
@@ -2063,7 +2061,8 @@ $root.AddUpcomingOpening = (function() {
     AddUpcomingOpening.encode = function encode(message, writer) {
         if (!writer)
             writer = $Writer.create();
-        $root.UpcomingOpeningMetadata.encode(message.metadata, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim();
+        if (message.metadata != null && Object.hasOwnProperty.call(message, "metadata"))
+            $root.UpcomingOpeningMetadata.encode(message.metadata, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim();
         return writer;
     };
 
@@ -2106,8 +2105,6 @@ $root.AddUpcomingOpening = (function() {
                 break;
             }
         }
-        if (!message.hasOwnProperty("metadata"))
-            throw $util.ProtocolError("missing required 'metadata'", { instance: message });
         return message;
     };
 
@@ -2138,7 +2135,7 @@ $root.AddUpcomingOpening = (function() {
     AddUpcomingOpening.verify = function verify(message) {
         if (typeof message !== "object" || message === null)
             return "object expected";
-        {
+        if (message.metadata != null && message.hasOwnProperty("metadata")) {
             var error = $root.UpcomingOpeningMetadata.verify(message.metadata);
             if (error)
                 return "metadata." + error;
@@ -2206,7 +2203,7 @@ $root.RemoveUpcomingOpening = (function() {
      * Properties of a RemoveUpcomingOpening.
      * @exports IRemoveUpcomingOpening
      * @interface IRemoveUpcomingOpening
-     * @property {string} id RemoveUpcomingOpening id
+     * @property {string|null} [id] RemoveUpcomingOpening id
      */
 
     /**
@@ -2256,7 +2253,8 @@ $root.RemoveUpcomingOpening = (function() {
     RemoveUpcomingOpening.encode = function encode(message, writer) {
         if (!writer)
             writer = $Writer.create();
-        writer.uint32(/* id 1, wireType 2 =*/10).string(message.id);
+        if (message.id != null && Object.hasOwnProperty.call(message, "id"))
+            writer.uint32(/* id 1, wireType 2 =*/10).string(message.id);
         return writer;
     };
 
@@ -2299,8 +2297,6 @@ $root.RemoveUpcomingOpening = (function() {
                 break;
             }
         }
-        if (!message.hasOwnProperty("id"))
-            throw $util.ProtocolError("missing required 'id'", { instance: message });
         return message;
     };
 
@@ -2331,8 +2327,9 @@ $root.RemoveUpcomingOpening = (function() {
     RemoveUpcomingOpening.verify = function verify(message) {
         if (typeof message !== "object" || message === null)
             return "object expected";
-        if (!$util.isString(message.id))
-            return "id: string expected";
+        if (message.id != null && message.hasOwnProperty("id"))
+            if (!$util.isString(message.id))
+                return "id: string expected";
         return null;
     };
 

+ 13 - 13
metadata-protobuf/proto/WorkingGroups.proto

@@ -1,27 +1,27 @@
 syntax = "proto2";
 
 message OpeningMetadata {
-  required string short_description = 1; // Short description of the opening
-  required string description = 2; // Full description of the opening
+  optional string short_description = 1; // Short description of the opening
+  optional string description = 2; // Full description of the opening
   optional uint32 hiring_limit = 3; // Expected number of hired applicants
   optional uint32 expected_ending_timestamp = 4; // Expected time when the opening will close (Unix timestamp)
-  required string application_details = 5; // Md-formatted text explaining the application process
+  optional string application_details = 5; // Md-formatted text explaining the application process
   message ApplicationFormQuestion {
-    required string question = 1; // The question itself (ie. "What is your name?"")
+    optional string question = 1; // The question itself (ie. "What is your name?"")
     enum InputType {
+      TEXTAREA = 0;
       TEXT = 1;
-      TEXTAREA = 2;
     }
-    required InputType type = 2; // Suggested type of the UI answer input
+    optional InputType type = 2; // Suggested type of the UI answer input
   }
   repeated ApplicationFormQuestion application_form_questions = 6; // List of questions that should be answered during application
 }
 
 message UpcomingOpeningMetadata {
-  required uint32 expected_start = 1; // Expected opening start (timestamp)
-  required uint64 reward_per_block = 2; // Expected reward per block
-  required uint64 min_application_stake = 3; // Expected min. application stake
-  required OpeningMetadata metadata = 4; // Opening metadata
+  optional uint32 expected_start = 1; // Expected opening start (timestamp)
+  optional uint64 reward_per_block = 2; // Expected reward per block
+  optional uint64 min_application_stake = 3; // Expected min. application stake
+  optional OpeningMetadata metadata = 4; // Opening metadata
 }
 
 message ApplicationMetadata {
@@ -38,15 +38,15 @@ message WorkingGroupMetadata {
 }
 
 message SetGroupMetadata {
-  required WorkingGroupMetadata new_metadata = 1; // New working group metadata to set (can be a partial update)
+  optional WorkingGroupMetadata new_metadata = 1; // New working group metadata to set (can be a partial update)
 }
 
 message AddUpcomingOpening {
-  required UpcomingOpeningMetadata metadata = 1; // Upcoming opening metadata
+  optional UpcomingOpeningMetadata metadata = 1; // Upcoming opening metadata
 }
 
 message RemoveUpcomingOpening {
-  required string id = 1; // Upcoming opening query-node id
+  optional string id = 1; // Upcoming opening query-node id
 }
 
 message WorkingGroupMetadataAction {

+ 4 - 4
query-node/mappings/membership.ts

@@ -79,8 +79,8 @@ async function newMembershipFromParams(
   const metadataEntity = new MemberMetadata({
     createdAt: eventTime,
     updatedAt: eventTime,
-    name: typeof metadata?.name === 'string' ? metadata.name : undefined,
-    about: typeof metadata?.about === 'string' ? metadata.about : undefined,
+    name: metadata?.name || undefined,
+    about: metadata?.about || undefined,
     // TODO: avatar
   })
 
@@ -159,11 +159,11 @@ export async function members_MemberProfileUpdated(db: DatabaseManager, event_:
   const eventTime = new Date(event_.blockTimestamp.toNumber())
 
   if (typeof metadata?.name === 'string') {
-    member.metadata.name = metadata.name
+    member.metadata.name = metadata.name || undefined
     member.metadata.updatedAt = eventTime
   }
   if (typeof metadata?.about === 'string') {
-    member.metadata.about = metadata.about
+    member.metadata.about = metadata.about || undefined
     member.metadata.updatedAt = eventTime
   }
   // TODO: avatar

+ 30 - 48
query-node/mappings/workingGroups.ts

@@ -10,6 +10,7 @@ import {
   IOpeningMetadata,
   IRemoveUpcomingOpening,
   ISetGroupMetadata,
+  IWorkingGroupMetadata,
   IWorkingGroupMetadataAction,
   OpeningMetadata,
   WorkingGroupMetadataAction,
@@ -135,46 +136,28 @@ async function getApplicationFormQuestions(
   return openingWithQuestions.metadata.applicationFormQuestions
 }
 
-function parseQuestionInputType(type: OpeningMetadata.ApplicationFormQuestion.InputType) {
-  if (type === OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA) {
-    return ApplicationFormQuestionType.TEXTAREA
-  }
-
-  return ApplicationFormQuestionType.TEXT
+const InputTypeToApplicationFormQuestionType = {
+  [OpeningMetadata.ApplicationFormQuestion.InputType.TEXT]: ApplicationFormQuestionType.TEXT,
+  [OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA]: ApplicationFormQuestionType.TEXTAREA,
 }
 
-function getDefaultOpeningMetadata(group: WorkingGroup, openingType: WorkingGroupOpeningType): OpeningMetadata {
-  const metadata = new OpeningMetadata({
-    shortDescription: `${_.startCase(group.name)} ${
-      openingType === WorkingGroupOpeningType.REGULAR ? 'worker' : 'leader'
-    } opening`,
-    description: `Apply to this opening in order to be considered for ${_.startCase(group.name)} ${
-      openingType === WorkingGroupOpeningType.REGULAR ? 'worker' : 'leader'
-    } role!`,
-    applicationDetails: `- Fill the application form`,
-    applicationFormQuestions: [
-      {
-        question: 'What makes you a good candidate?',
-        type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA,
-      },
-    ],
-  })
-
-  return metadata
+function parseQuestionInputType(
+  type?: OpeningMetadata.ApplicationFormQuestion.InputType | null
+): ApplicationFormQuestionType {
+  const validType: OpeningMetadata.ApplicationFormQuestion.InputType = type || 0
+  return InputTypeToApplicationFormQuestionType[validType]
 }
 
 async function createOpeningMeta(
   db: DatabaseManager,
   event_: SubstrateEvent,
-  group: WorkingGroup,
-  openingType: WorkingGroupOpeningType,
   originalMeta: Bytes | IOpeningMetadata
 ): Promise<WorkingGroupOpeningMetadata> {
   let originallyValid: boolean
   let metadata: IOpeningMetadata
   if (originalMeta instanceof Bytes) {
     const deserializedMetadata = await deserializeMetadata(OpeningMetadata, originalMeta)
-    metadata = deserializedMetadata || (await getDefaultOpeningMetadata(group, openingType))
+    metadata = deserializedMetadata || {}
     originallyValid = !!deserializedMetadata
   } else {
     metadata = originalMeta
@@ -195,10 +178,10 @@ async function createOpeningMeta(
     createdAt: eventTime,
     updatedAt: eventTime,
     originallyValid,
-    applicationDetails,
-    description,
-    shortDescription,
-    hiringLimit: typeof hiringLimit === 'number' ? hiringLimit : undefined,
+    applicationDetails: applicationDetails || undefined,
+    description: description || undefined,
+    shortDescription: shortDescription || undefined,
+    hiringLimit: hiringLimit || undefined,
     expectedEnding: expectedEndingTimestamp ? new Date(expectedEndingTimestamp) : undefined,
     applicationFormQuestions: [],
   })
@@ -210,7 +193,7 @@ async function createOpeningMeta(
       const applicationFormQuestion = new ApplicationFormQuestion({
         createdAt: eventTime,
         updatedAt: eventTime,
-        question,
+        question: question || undefined,
         type: parseQuestionInputType(type),
         index,
         openingMetadata,
@@ -256,24 +239,19 @@ async function handleAddUpcomingOpeningAction(
   statusChangedEvent: StatusTextChangedEvent,
   action: IAddUpcomingOpening
 ): Promise<UpcomingOpeningAdded | InvalidActionMetadata> {
-  const upcomingOpeningMeta = action.metadata
+  const upcomingOpeningMeta = action.metadata || {}
   const group = await getWorkingGroup(db, event_)
   const eventTime = new Date(event_.blockTimestamp.toNumber())
-  const openingMeta = await createOpeningMeta(
-    db,
-    event_,
-    group,
-    WorkingGroupOpeningType.REGULAR,
-    action.metadata.metadata
-  )
+  const openingMeta = await createOpeningMeta(db, event_, upcomingOpeningMeta.metadata || {})
+  const { rewardPerBlock, expectedStart, minApplicationStake } = upcomingOpeningMeta
   const upcomingOpening = new UpcomingWorkingGroupOpening({
     createdAt: eventTime,
     updatedAt: eventTime,
     metadata: openingMeta,
     group,
-    rewardPerBlock: new BN(upcomingOpeningMeta.rewardPerBlock.toString()),
-    expectedStart: new Date(upcomingOpeningMeta.expectedStart),
-    stakeAmount: new BN(upcomingOpeningMeta.minApplicationStake.toString()),
+    rewardPerBlock: rewardPerBlock?.toNumber() ? new BN(rewardPerBlock.toString()) : undefined,
+    expectedStart: expectedStart ? new Date(expectedStart) : undefined,
+    stakeAmount: minApplicationStake?.toNumber() ? new BN(minApplicationStake.toString()) : undefined,
     createdInEvent: statusChangedEvent,
     createdAtBlock: await getOrCreateBlock(db, event_),
   })
@@ -313,17 +291,21 @@ async function handleSetWorkingGroupMetadataAction(
 ): Promise<WorkingGroupMetadataSet> {
   const { newMetadata } = action
   const group = await getWorkingGroup(db, event_, ['metadata'])
-  const groupMetadata = group.metadata
+  const oldMetadata = group.metadata
   const eventTime = new Date(event_.blockTimestamp.toNumber())
+  const setNewOptionalString = (field: keyof IWorkingGroupMetadata) =>
+    typeof newMetadata?.[field] === 'string' ? newMetadata[field] || undefined : oldMetadata?.[field]
 
   const newGroupMetadata = new WorkingGroupMetadata({
-    ..._.merge(groupMetadata, newMetadata),
-    id: undefined,
     createdAt: eventTime,
     updatedAt: eventTime,
     setAtBlock: await getOrCreateBlock(db, event_),
     setInEvent: statusChangedEvent,
     group,
+    status: setNewOptionalString('status'),
+    statusMessage: setNewOptionalString('statusMessage'),
+    about: setNewOptionalString('about'),
+    description: setNewOptionalString('description'),
   })
   await db.save<WorkingGroupMetadata>(newGroupMetadata)
 
@@ -351,7 +333,7 @@ async function handleWorkingGroupMetadataAction(
     return handleSetWorkingGroupMetadataAction(db, event_, statusChangedEvent, action.setGroupMetadata)
   } else {
     const result = new InvalidActionMetadata()
-    result.reason = 'Unexpected action type'
+    result.reason = 'No known action was provided'
     return result
   }
 }
@@ -416,7 +398,7 @@ export async function workingGroups_OpeningAdded(db: DatabaseManager, event_: Su
     type: openingType.isLeader ? WorkingGroupOpeningType.LEADER : WorkingGroupOpeningType.REGULAR,
   })
 
-  const metadata = await createOpeningMeta(db, event_, group, opening.type, metadataBytes)
+  const metadata = await createOpeningMeta(db, event_, metadataBytes)
   opening.metadata = metadata
 
   await db.save<WorkingGroupOpening>(opening)

+ 8 - 8
query-node/schemas/workingGroups.graphql

@@ -151,10 +151,10 @@ type WorkingGroupOpeningMetadata @entity {
   originallyValid: Boolean!
 
   "Opening short description"
-  shortDescription: String!
+  shortDescription: String
 
   "Opening description (md-formatted)"
-  description: String!
+  description: String
 
   "Expected max. number of applicants that will be hired"
   hiringLimit: Int
@@ -163,10 +163,10 @@ type WorkingGroupOpeningMetadata @entity {
   expectedEnding: DateTime
 
   "Md-formatted text explaining the application process"
-  applicationDetails: String!
+  applicationDetails: String
 
   "List of questions that should be answered during application"
-  applicationFormQuestions: [ApplicationFormQuestion!]! @derivedFrom(field: "openingMetadata")
+  applicationFormQuestions: [ApplicationFormQuestion!] @derivedFrom(field: "openingMetadata")
 }
 
 type WorkingGroupOpening @entity {
@@ -218,13 +218,13 @@ type UpcomingWorkingGroupOpening @entity {
   group: WorkingGroup!
 
   "Expected opening start time"
-  expectedStart: DateTime!
+  expectedStart: DateTime
 
   "Expected min. application/role stake amount"
-  stakeAmount: BigInt!
+  stakeAmount: BigInt
 
   "Expected reward per block"
-  rewardPerBlock: BigInt!
+  rewardPerBlock: BigInt
 
   "Opening metadata"
   metadata: WorkingGroupOpeningMetadata!
@@ -320,7 +320,7 @@ type ApplicationFormQuestion @entity {
   openingMetadata: WorkingGroupOpeningMetadata!
 
   "The question itself"
-  question: String!
+  question: String
 
   "Type of the question (UI answer input type)"
   type: ApplicationFormQuestionType!

+ 2 - 2
tests/integration-tests/src/fixtures/membership/BuyMembershipHappyCaseFixture.ts

@@ -78,8 +78,8 @@ export class BuyMembershipHappyCaseFixture extends BaseMembershipFixture {
     assert.equal(qEvent.handle, txParams.handle)
     assert.equal(qEvent.rootAccount, txParams.root_account.toString())
     assert.equal(qEvent.controllerAccount, txParams.controller_account.toString())
-    assert.equal(qEvent.metadata.name, metadata.name)
-    assert.equal(qEvent.metadata.about, metadata.about)
+    assert.equal(qEvent.metadata.name, metadata.name || null)
+    assert.equal(qEvent.metadata.about, metadata.about || null)
     // TODO: avatar
   }
 

+ 22 - 104
tests/integration-tests/src/fixtures/workingGroups/BaseCreateOpeningFixture.ts

@@ -1,114 +1,29 @@
-import BN from 'bn.js'
-import { IOpeningMetadata, OpeningMetadata } from '@joystream/metadata-protobuf'
+import { IOpeningMetadata } from '@joystream/metadata-protobuf'
 import { assert } from 'chai'
-import { Api } from '../../Api'
-import { MIN_APPLICATION_STAKE, MIN_USTANKING_PERIOD } from '../../consts'
 import { OpeningMetadataFieldsFragment } from '../../graphql/generated/queries'
-import { QueryNodeApi } from '../../QueryNodeApi'
-import { WorkingGroupModuleName } from '../../types'
 import { BaseWorkingGroupFixture } from './BaseWorkingGroupFixture'
 import { queryNodeQuestionTypeToMetadataQuestionType } from './utils'
-import { Bytes } from '@polkadot/types'
-import _ from 'lodash'
-import { Utils } from '../../utils'
-import moment from 'moment'
-import { createType } from '@joystream/types'
-
-export type OpeningParams = {
-  stake: BN
-  unstakingPeriod: number
-  reward: BN
-  metadata: IOpeningMetadata | string
-  expectMetadataFailue?: boolean
-}
-
-export type UpcomingOpeningParams = OpeningParams & {
-  expectedStartTs: number
-}
-
 export abstract class BaseCreateOpeningFixture extends BaseWorkingGroupFixture {
-  protected openingsParams: OpeningParams[]
-
-  public constructor(
-    api: Api,
-    query: QueryNodeApi,
-    group: WorkingGroupModuleName,
-    openingsParams?: Partial<OpeningParams>[]
-  ) {
-    super(api, query, group)
-    this.openingsParams = (openingsParams || [{}]).map((params) => ({ ...this.defaultOpeningParams, ...params }))
-  }
-
-  protected defaultOpeningParams: Omit<OpeningParams, 'metadata'> & { metadata: IOpeningMetadata } = {
-    stake: MIN_APPLICATION_STAKE,
-    unstakingPeriod: MIN_USTANKING_PERIOD,
-    reward: new BN(10),
-    metadata: {
-      shortDescription: 'Test opening',
-      description: '# Test opening',
-      expectedEndingTimestamp: moment().unix() + 60,
-      hiringLimit: 1,
-      applicationDetails: '- This is automatically created opening, do not apply!',
-      applicationFormQuestions: [
-        { question: 'Question 1?', type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXT },
-        { question: 'Question 2?', type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA },
-      ],
-    },
-  }
-
-  public getDefaultOpeningParams(): Omit<OpeningParams, 'metadata'> & { metadata: IOpeningMetadata } {
-    return this.defaultOpeningParams
-  }
-
-  protected getMetadata(openingParams: OpeningParams): IOpeningMetadata | null {
-    const metadataObjOrHex = openingParams.metadata
-    if (typeof metadataObjOrHex === 'string') {
-      try {
-        return Utils.metadataFromBytes(OpeningMetadata, createType('Bytes', metadataObjOrHex))
-      } catch (e) {
-        if (!openingParams.expectMetadataFailue) {
-          throw e
-        }
-        return null
-      }
-    }
-
-    return metadataObjOrHex
-  }
-
-  protected getMetadataBytes(openingParams: OpeningParams): Bytes {
-    const { metadata } = openingParams
-    return typeof metadata === 'string'
-      ? createType('Bytes', metadata)
-      : Utils.metadataToBytes(OpeningMetadata, metadata)
-  }
-
-  protected getDefaultQueryNodeMetadata(isLeadOpening: boolean): IOpeningMetadata {
-    return {
-      shortDescription: `${_.startCase(this.group)} ${!isLeadOpening ? 'worker' : 'leader'} opening`,
-      description: `Apply to this opening in order to be considered for ${_.startCase(this.group)} ${
-        !isLeadOpening ? 'worker' : 'leader'
-      } role!`,
-      applicationDetails: `- Fill the application form`,
-      applicationFormQuestions: [
-        {
-          question: 'What makes you a good candidate?',
-          type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA,
-        },
-      ],
-    }
-  }
-
   protected assertQueriedOpeningMetadataIsValid(
     qOpeningMeta: OpeningMetadataFieldsFragment,
-    expectedMetadata: IOpeningMetadata | null
+    expectedMetadata?: IOpeningMetadata | null
   ): void {
-    Utils.assert(expectedMetadata, 'Expected metadata cannot be invalid!')
-    assert.equal(qOpeningMeta.shortDescription, expectedMetadata.shortDescription)
-    assert.equal(qOpeningMeta.description, expectedMetadata.description)
-    assert.equal(new Date(qOpeningMeta.expectedEnding).getTime(), expectedMetadata.expectedEndingTimestamp)
-    assert.equal(qOpeningMeta.hiringLimit, expectedMetadata.hiringLimit)
-    assert.equal(qOpeningMeta.applicationDetails, expectedMetadata.applicationDetails)
+    const {
+      shortDescription,
+      description,
+      expectedEndingTimestamp,
+      hiringLimit,
+      applicationDetails,
+      applicationFormQuestions,
+    } = expectedMetadata || {}
+    assert.equal(qOpeningMeta.shortDescription, shortDescription || null)
+    assert.equal(qOpeningMeta.description, description || null)
+    assert.equal(
+      qOpeningMeta.expectedEnding ? new Date(qOpeningMeta.expectedEnding).getTime() : qOpeningMeta.expectedEnding,
+      expectedEndingTimestamp || null
+    )
+    assert.equal(qOpeningMeta.hiringLimit, hiringLimit || null)
+    assert.equal(qOpeningMeta.applicationDetails, applicationDetails || null)
     assert.deepEqual(
       qOpeningMeta.applicationFormQuestions
         .sort((a, b) => a.index - b.index)
@@ -116,7 +31,10 @@ export abstract class BaseCreateOpeningFixture extends BaseWorkingGroupFixture {
           question,
           type: queryNodeQuestionTypeToMetadataQuestionType(type),
         })),
-      expectedMetadata.applicationFormQuestions
+      (applicationFormQuestions || []).map(({ question, type }) => ({
+        question: question || null,
+        type: type || 0,
+      }))
     )
   }
 }

+ 61 - 9
tests/integration-tests/src/fixtures/workingGroups/CreateOpeningsFixture.ts

@@ -1,5 +1,5 @@
 import { Api } from '../../Api'
-import { BaseCreateOpeningFixture, OpeningParams } from './BaseCreateOpeningFixture'
+import { BaseCreateOpeningFixture } from './BaseCreateOpeningFixture'
 import { QueryNodeApi } from '../../QueryNodeApi'
 import { OpeningAddedEventDetails, WorkingGroupModuleName } from '../../types'
 import { OpeningId } from '@joystream/types/working-group'
@@ -9,19 +9,53 @@ import { ISubmittableResult } from '@polkadot/types/types/'
 import { OpeningAddedEventFieldsFragment, OpeningFieldsFragment } from '../../graphql/generated/queries'
 import { EventType, WorkingGroupOpeningType } from '../../graphql/generated/schema'
 import { assert } from 'chai'
+import { MIN_APPLICATION_STAKE, MIN_UNSTANKING_PERIOD } from '../../consts'
+import moment from 'moment'
+import BN from 'bn.js'
+import { IOpeningMetadata, OpeningMetadata } from '@joystream/metadata-protobuf'
+import { createType } from '@joystream/types'
+import { Bytes } from '@polkadot/types'
+
+export type OpeningParams = {
+  stake: BN
+  unstakingPeriod: number
+  reward: BN
+  metadata: IOpeningMetadata | string
+  expectMetadataFailure?: boolean
+}
+
+export const DEFAULT_OPENING_PARAMS: Omit<OpeningParams, 'metadata'> & { metadata: IOpeningMetadata } = {
+  stake: MIN_APPLICATION_STAKE,
+  unstakingPeriod: MIN_UNSTANKING_PERIOD,
+  reward: new BN(10),
+  metadata: {
+    shortDescription: 'Test opening',
+    description: '# Test opening',
+    expectedEndingTimestamp: moment().unix() + 60,
+    hiringLimit: 1,
+    applicationDetails: '- This is automatically created opening, do not apply!',
+    applicationFormQuestions: [
+      { question: 'Question 1?', type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXT },
+      { question: 'Question 2?', type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA },
+    ],
+  },
+}
 
 export class CreateOpeningsFixture extends BaseCreateOpeningFixture {
   protected asSudo: boolean
   protected events: OpeningAddedEventDetails[] = []
 
+  protected openingsParams: OpeningParams[]
+
   public constructor(
     api: Api,
     query: QueryNodeApi,
     group: WorkingGroupModuleName,
-    openingsParams?: Partial<OpeningParams>[],
+    openingsParams?: OpeningParams[],
     asSudo = false
   ) {
-    super(api, query, group, openingsParams)
+    super(api, query, group)
+    this.openingsParams = openingsParams || [DEFAULT_OPENING_PARAMS]
     this.asSudo = asSudo
   }
 
@@ -36,10 +70,32 @@ export class CreateOpeningsFixture extends BaseCreateOpeningFixture {
     return this.asSudo ? (await this.api.query.sudo.key()).toString() : await this.api.getLeadRoleKey(this.group)
   }
 
+  protected getOpeningMetadata(params: OpeningParams): IOpeningMetadata | null {
+    if (typeof params.metadata === 'string') {
+      try {
+        return Utils.metadataFromBytes(OpeningMetadata, createType('Bytes', params.metadata))
+      } catch (e) {
+        if (!params.expectMetadataFailure) {
+          throw e
+        }
+        return null
+      }
+    }
+
+    return params.metadata
+  }
+
+  protected getOpeningMetadataBytes(params: { metadata: IOpeningMetadata | string }): Bytes {
+    const { metadata } = params
+    return typeof metadata === 'string'
+      ? createType('Bytes', metadata)
+      : Utils.metadataToBytes(OpeningMetadata, metadata)
+  }
+
   protected async getExtrinsics(): Promise<SubmittableExtrinsic<'promise'>[]> {
     const extrinsics = this.openingsParams.map((params) =>
       this.api.tx[this.group].addOpening(
-        this.getMetadataBytes(params),
+        this.getOpeningMetadataBytes(params),
         this.asSudo ? 'Leader' : 'Regular',
         { stake_amount: params.stake, leaving_unstaking_period: params.unstakingPeriod },
         params.reward
@@ -67,11 +123,7 @@ export class CreateOpeningsFixture extends BaseCreateOpeningFixture {
       assert.equal(qOpening.stakeAmount, openingParams.stake.toString())
       assert.equal(qOpening.unstakingPeriod, openingParams.unstakingPeriod)
       // Metadata
-      if (openingParams.expectMetadataFailue) {
-        this.assertQueriedOpeningMetadataIsValid(qOpening.metadata, this.getDefaultQueryNodeMetadata(this.asSudo))
-      } else {
-        this.assertQueriedOpeningMetadataIsValid(qOpening.metadata, this.getMetadata(openingParams))
-      }
+      this.assertQueriedOpeningMetadataIsValid(qOpening.metadata, this.getOpeningMetadata(openingParams))
     })
   }
 

+ 68 - 41
tests/integration-tests/src/fixtures/workingGroups/CreateUpcomingOpeningsFixture.ts

@@ -1,5 +1,5 @@
 import { Api } from '../../Api'
-import { BaseCreateOpeningFixture, UpcomingOpeningParams } from './BaseCreateOpeningFixture'
+import { BaseCreateOpeningFixture } from './BaseCreateOpeningFixture'
 import { QueryNodeApi } from '../../QueryNodeApi'
 import { EventDetails, WorkingGroupModuleName } from '../../types'
 import { SubmittableExtrinsic } from '@polkadot/api/types'
@@ -8,31 +8,36 @@ import { ISubmittableResult } from '@polkadot/types/types/'
 import { StatusTextChangedEventFieldsFragment, UpcomingOpeningFieldsFragment } from '../../graphql/generated/queries'
 import { EventType } from '../../graphql/generated/schema'
 import { assert } from 'chai'
-import { IOpeningMetadata, WorkingGroupMetadataAction } from '@joystream/metadata-protobuf'
-import _ from 'lodash'
+import {
+  IUpcomingOpeningMetadata,
+  UpcomingOpeningMetadata,
+  WorkingGroupMetadataAction,
+} from '@joystream/metadata-protobuf'
 import Long from 'long'
 import { Bytes } from '@polkadot/types'
 import moment from 'moment'
+import { DEFAULT_OPENING_PARAMS } from './CreateOpeningsFixture'
+import { createType } from '@joystream/types'
+
+export const DEFAULT_UPCOMING_OPENING_META: IUpcomingOpeningMetadata = {
+  minApplicationStake: Long.fromString(DEFAULT_OPENING_PARAMS.stake.toString()),
+  rewardPerBlock: Long.fromString(DEFAULT_OPENING_PARAMS.reward.toString()),
+  expectedStart: moment().unix() + 3600,
+  metadata: DEFAULT_OPENING_PARAMS.metadata,
+}
+
+export type UpcomingOpeningParams = {
+  meta: IUpcomingOpeningMetadata | string
+  expectMetadataFailure?: boolean
+}
 
 export class CreateUpcomingOpeningsFixture extends BaseCreateOpeningFixture {
-  protected openingsParams: UpcomingOpeningParams[]
+  protected upcomingOpeningsParams: UpcomingOpeningParams[]
   protected createdUpcomingOpeningIds: string[] = []
 
-  public getDefaultOpeningParams(): Omit<UpcomingOpeningParams, 'metadata'> & { metadata: IOpeningMetadata } {
-    return {
-      ...super.getDefaultOpeningParams(),
-      expectedStartTs: moment().unix() + 3600,
-    }
-  }
-
-  public constructor(
-    api: Api,
-    query: QueryNodeApi,
-    group: WorkingGroupModuleName,
-    openingsParams?: Partial<UpcomingOpeningParams>[]
-  ) {
-    super(api, query, group, openingsParams)
-    this.openingsParams = (openingsParams || [{}]).map((params) => ({ ...this.getDefaultOpeningParams(), ...params }))
+  public constructor(api: Api, query: QueryNodeApi, group: WorkingGroupModuleName, params?: UpcomingOpeningParams[]) {
+    super(api, query, group)
+    this.upcomingOpeningsParams = params || [{ meta: DEFAULT_UPCOMING_OPENING_META }]
   }
 
   protected async getSignerAccountOrAccounts(): Promise<string> {
@@ -40,7 +45,7 @@ export class CreateUpcomingOpeningsFixture extends BaseCreateOpeningFixture {
   }
 
   protected async getExtrinsics(): Promise<SubmittableExtrinsic<'promise'>[]> {
-    return this.openingsParams.map((params) =>
+    return this.upcomingOpeningsParams.map((params) =>
       this.api.tx[this.group].setStatusText(this.getActionMetadataBytes(params))
     )
   }
@@ -56,21 +61,28 @@ export class CreateUpcomingOpeningsFixture extends BaseCreateOpeningFixture {
     return this.createdUpcomingOpeningIds
   }
 
-  protected getActionMetadataBytes(openingParams: UpcomingOpeningParams): Bytes {
-    const openingMetadata = this.getMetadata(openingParams)
-    console.log('OpeningMetadata', openingMetadata)
-    if (!openingMetadata) {
-      // Opening metadata is invalid so we just return the provided bytes
-      return this.getMetadataBytes(openingParams)
+  protected getUpcomingOpeningMeta(params: UpcomingOpeningParams): IUpcomingOpeningMetadata | null {
+    if (typeof params.meta === 'string') {
+      try {
+        return Utils.metadataFromBytes(UpcomingOpeningMetadata, createType('Bytes', params.meta))
+      } catch (e) {
+        if (!params.expectMetadataFailure) {
+          throw e
+        }
+        return null
+      }
+    }
+    return params.meta
+  }
+
+  protected getActionMetadataBytes(params: UpcomingOpeningParams): Bytes {
+    const upcomingOpeningMeta = this.getUpcomingOpeningMeta(params)
+    if (!upcomingOpeningMeta) {
+      return createType('Bytes', params.meta)
     }
     return Utils.metadataToBytes(WorkingGroupMetadataAction, {
       addUpcomingOpening: {
-        metadata: {
-          expectedStart: openingParams.expectedStartTs,
-          minApplicationStake: Long.fromString(openingParams.stake.toString()),
-          rewardPerBlock: Long.fromString(openingParams.reward.toString()),
-          metadata: openingMetadata,
-        },
+        metadata: upcomingOpeningMeta,
       },
     })
   }
@@ -80,19 +92,34 @@ export class CreateUpcomingOpeningsFixture extends BaseCreateOpeningFixture {
     qEvents: StatusTextChangedEventFieldsFragment[]
   ): void {
     this.events.forEach((e, i) => {
-      const openingParams = this.openingsParams[i]
+      const expectedMeta = this.getUpcomingOpeningMeta(this.upcomingOpeningsParams[i])
       const qEvent = this.findMatchingQueryNodeEvent(e, qEvents)
       const qUpcomingOpening = qUpcomingOpenings.find((o) => o.createdInEvent.id === qEvent.id)
-      if (!openingParams.expectMetadataFailue) {
+      if (expectedMeta) {
         Utils.assert(qUpcomingOpening)
-        assert.equal(new Date(qUpcomingOpening.expectedStart).getTime(), openingParams.expectedStartTs)
+        assert.equal(
+          qUpcomingOpening.expectedStart
+            ? new Date(qUpcomingOpening.expectedStart).getTime()
+            : qUpcomingOpening.expectedStart,
+          expectedMeta.expectedStart || null
+        )
         assert.equal(qUpcomingOpening.group.name, this.group)
-        assert.equal(qUpcomingOpening.rewardPerBlock, openingParams.reward.toString())
-        assert.equal(qUpcomingOpening.stakeAmount, openingParams.stake.toString())
+        assert.equal(
+          qUpcomingOpening.rewardPerBlock,
+          expectedMeta.rewardPerBlock && expectedMeta.rewardPerBlock.toNumber()
+            ? expectedMeta.rewardPerBlock.toString()
+            : null
+        )
+        assert.equal(
+          qUpcomingOpening.stakeAmount,
+          expectedMeta.minApplicationStake && expectedMeta.minApplicationStake.toNumber()
+            ? expectedMeta.minApplicationStake.toString()
+            : null
+        )
         assert.equal(qUpcomingOpening.createdAtBlock.number, e.blockNumber)
         Utils.assert(qEvent.result.__typename === 'UpcomingOpeningAdded')
         assert.equal(qEvent.result.upcomingOpeningId, qUpcomingOpening.id)
-        this.assertQueriedOpeningMetadataIsValid(qUpcomingOpening.metadata, this.getMetadata(openingParams))
+        this.assertQueriedOpeningMetadataIsValid(qUpcomingOpening.metadata, expectedMeta.metadata)
       } else {
         assert.isUndefined(qUpcomingOpening)
       }
@@ -100,13 +127,13 @@ export class CreateUpcomingOpeningsFixture extends BaseCreateOpeningFixture {
   }
 
   protected assertQueryNodeEventIsValid(qEvent: StatusTextChangedEventFieldsFragment, i: number): void {
-    const openingParams = this.openingsParams[i]
+    const params = this.upcomingOpeningsParams[i]
     assert.equal(qEvent.event.type, EventType.StatusTextChanged)
     assert.equal(qEvent.group.name, this.group)
-    assert.equal(qEvent.metadata, this.getActionMetadataBytes(openingParams).toString())
+    assert.equal(qEvent.metadata, this.getActionMetadataBytes(params).toString())
     assert.equal(
       qEvent.result.__typename,
-      openingParams.expectMetadataFailue ? 'InvalidActionMetadata' : 'UpcomingOpeningAdded'
+      params.expectMetadataFailure ? 'InvalidActionMetadata' : 'UpcomingOpeningAdded'
     )
   }
 

+ 2 - 2
tests/integration-tests/src/fixtures/workingGroups/HireWorkersFixture.ts

@@ -7,7 +7,7 @@ import { WorkingGroupModuleName } from '../../types'
 import { Utils } from '../../utils'
 import { AddStakingAccountsHappyCaseFixture, BuyMembershipHappyCaseFixture } from '../membership'
 import { ApplicantDetails, ApplyOnOpeningsHappyCaseFixture } from './ApplyOnOpeningsHappyCaseFixture'
-import { CreateOpeningsFixture } from './CreateOpeningsFixture'
+import { CreateOpeningsFixture, DEFAULT_OPENING_PARAMS } from './CreateOpeningsFixture'
 import { FillOpeningsFixture } from './FillOpeningsFixture'
 
 export class HireWorkersFixture extends BaseQueryNodeFixture {
@@ -38,7 +38,7 @@ export class HireWorkersFixture extends BaseQueryNodeFixture {
     const openingRunner = new FixtureRunner(createOpeningFixture)
     await openingRunner.run()
     const [openingId] = createOpeningFixture.getCreatedOpeningIds()
-    const { stake: openingStake, metadata: openingMetadata } = createOpeningFixture.getDefaultOpeningParams()
+    const { stake: openingStake, metadata: openingMetadata } = DEFAULT_OPENING_PARAMS
 
     // Create the applications
     const roleAccounts = (await this.api.createKeyPairs(this.workersN)).map((kp) => kp.address)

+ 7 - 5
tests/integration-tests/src/fixtures/workingGroups/UpdateGroupStatusFixture.ts

@@ -73,11 +73,13 @@ export class UpdateGroupStatusFixture extends BaseWorkingGroupFixture {
     if (!postUpdateSnapshot) {
       throw new Error('Query node: WorkingGroupMetadata snapshot not found!')
     }
-    const expectedMeta = _.merge(preUpdateSnapshot, update)
-    assert.equal(postUpdateSnapshot.status, expectedMeta.status)
-    assert.equal(postUpdateSnapshot.statusMessage, expectedMeta.statusMessage)
-    assert.equal(postUpdateSnapshot.description, expectedMeta.description)
-    assert.equal(postUpdateSnapshot.about, expectedMeta.about)
+    const expectedMeta = _.mergeWith(preUpdateSnapshot, update, (destValue, sourceValue) =>
+      sourceValue === null || sourceValue === undefined ? destValue : sourceValue
+    )
+    assert.equal(postUpdateSnapshot.status, expectedMeta.status || null)
+    assert.equal(postUpdateSnapshot.statusMessage, expectedMeta.statusMessage || null)
+    assert.equal(postUpdateSnapshot.description, expectedMeta.description || null)
+    assert.equal(postUpdateSnapshot.about, expectedMeta.about || null)
     assert.equal(postUpdateSnapshot.setAtBlock.number, eventDetails.blockNumber)
   }
 

+ 6 - 2
tests/integration-tests/src/fixtures/workingGroups/index.ts

@@ -4,8 +4,12 @@ export {
   OpeningApplications,
 } from './ApplyOnOpeningsHappyCaseFixture'
 export { CancelOpeningsFixture } from './CancelOpeningsFixture'
-export { CreateOpeningsFixture } from './CreateOpeningsFixture'
-export { CreateUpcomingOpeningsFixture } from './CreateUpcomingOpeningsFixture'
+export { DEFAULT_OPENING_PARAMS, OpeningParams, CreateOpeningsFixture } from './CreateOpeningsFixture'
+export {
+  DEFAULT_UPCOMING_OPENING_META,
+  UpcomingOpeningParams,
+  CreateUpcomingOpeningsFixture,
+} from './CreateUpcomingOpeningsFixture'
 export { FillOpeningsFixture } from './FillOpeningsFixture'
 export { RemoveUpcomingOpeningsFixture } from './RemoveUpcomingOpeningsFixture'
 export { UpdateGroupStatusFixture } from './UpdateGroupStatusFixture'

+ 1 - 1
tests/integration-tests/src/flows/working-groups/groupStatus.ts

@@ -36,7 +36,7 @@ export default async function groupStatus({ api, query, env }: FlowProps): Promi
           about: null,
           description: null,
         },
-        // Should change everything to empty strings:
+        // Should unset everything:
         {
           status: '',
           statusMessage: '',

+ 2 - 1
tests/integration-tests/src/flows/working-groups/leadOpening.ts

@@ -4,6 +4,7 @@ import {
   CreateOpeningsFixture,
   FillOpeningsFixture,
   ApplicantDetails,
+  DEFAULT_OPENING_PARAMS,
 } from '../../fixtures/workingGroups'
 
 import Debugger from 'debug'
@@ -22,7 +23,7 @@ export default async function leadOpening({ api, query, env }: FlowProps): Promi
       const openingRunner = new FixtureRunner(createOpeningFixture)
       await openingRunner.run()
       const [openingId] = createOpeningFixture.getCreatedOpeningIds()
-      const { stake: openingStake, metadata: openingMetadata } = createOpeningFixture.getDefaultOpeningParams()
+      const { stake: openingStake, metadata: openingMetadata } = DEFAULT_OPENING_PARAMS
 
       const [roleAccount, stakingAccount, rewardAccount] = (await api.createKeyPairs(3)).map((kp) => kp.address)
       const buyMembershipFixture = new BuyMembershipHappyCaseFixture(api, query, [roleAccount])

+ 55 - 8
tests/integration-tests/src/flows/working-groups/openingAndApplicationStatus.ts → tests/integration-tests/src/flows/working-groups/openingsAndApplications.ts

@@ -5,6 +5,8 @@ import {
   CreateOpeningsFixture,
   WithdrawApplicationsFixture,
   ApplicantDetails,
+  DEFAULT_OPENING_PARAMS,
+  OpeningParams,
 } from '../../fixtures/workingGroups'
 
 import Debugger from 'debug'
@@ -13,7 +15,52 @@ import { AddStakingAccountsHappyCaseFixture, BuyMembershipHappyCaseFixture } fro
 import { workingGroups, LEADER_OPENING_STAKE } from '../../consts'
 import { assert } from 'chai'
 
-export default async function openingAndApplicationStatusFlow({ api, query, env }: FlowProps): Promise<void> {
+const openingsToCreate: OpeningParams[] = [
+  // All defaults case:
+  DEFAULT_OPENING_PARAMS,
+  // Invalid metadata case:
+  {
+    ...DEFAULT_OPENING_PARAMS,
+    metadata: '0xff',
+    expectMetadataFailure: true,
+  },
+  // Valid metadata edge-cases:
+  {
+    ...DEFAULT_OPENING_PARAMS,
+    metadata: {
+      shortDescription: '',
+      description: '',
+      expectedEndingTimestamp: 0,
+      hiringLimit: 0,
+      applicationDetails: '',
+      applicationFormQuestions: [],
+    },
+  },
+  {
+    ...DEFAULT_OPENING_PARAMS,
+    metadata: {
+      shortDescription: null,
+      description: null,
+      expectedEndingTimestamp: null,
+      hiringLimit: null,
+      applicationDetails: null,
+      applicationFormQuestions: null,
+    },
+  },
+  {
+    ...DEFAULT_OPENING_PARAMS,
+    metadata: {},
+  },
+  {
+    ...DEFAULT_OPENING_PARAMS,
+    metadata: {
+      hiringLimit: 1,
+      applicationFormQuestions: [{}],
+    },
+  },
+]
+
+export default async function openingsAndApplications({ api, query, env }: FlowProps): Promise<void> {
   const APPLICATION_CREATE_N = parseInt(env.APPLICATION_STATUS_CREATE_N || '')
   const APPLICATION_WITHDRAW_N = parseInt(env.APPLICATION_STATUS_WITHDRAW_N || '')
   assert.isAbove(APPLICATION_CREATE_N, 0)
@@ -22,20 +69,20 @@ export default async function openingAndApplicationStatusFlow({ api, query, env
 
   await Promise.all(
     workingGroups.map(async (group) => {
-      const debug = Debugger(`flow:opening-and-application-status:${group}`)
+      const debug = Debugger(`flow:openings-and-applications:${group}`)
       debug('Started')
       api.enableDebugTxLogs()
 
       // Transfer funds to leader staking acc to cover opening stake
       const leaderStakingAcc = await api.getLeaderStakingKey(group)
-      await api.treasuryTransferBalance(leaderStakingAcc, LEADER_OPENING_STAKE)
+      await api.treasuryTransferBalance(leaderStakingAcc, LEADER_OPENING_STAKE.muln(openingsToCreate.length))
 
       // Create an opening
-      const createOpeningFixture = new CreateOpeningsFixture(api, query, group)
-      const openingRunner = new FixtureRunner(createOpeningFixture)
-      await openingRunner.run()
-      const [openingId] = createOpeningFixture.getCreatedOpeningIds()
-      const { stake: openingStake, metadata: openingMetadata } = createOpeningFixture.getDefaultOpeningParams()
+      const createOpeningsFixture = new CreateOpeningsFixture(api, query, group, openingsToCreate)
+      const openingsRunner = new FixtureRunner(createOpeningsFixture)
+      await openingsRunner.run()
+      const [openingId] = createOpeningsFixture.getCreatedOpeningIds()
+      const { stake: openingStake, metadata: openingMetadata } = DEFAULT_OPENING_PARAMS
 
       // Create some applications
       const roleAccounts = (await api.createKeyPairs(APPLICATION_CREATE_N)).map((kp) => kp.address)

+ 74 - 28
tests/integration-tests/src/flows/working-groups/upcomingOpenings.ts

@@ -1,10 +1,69 @@
 import { FlowProps } from '../../Flow'
-import { CreateUpcomingOpeningsFixture, RemoveUpcomingOpeningsFixture } from '../../fixtures/workingGroups'
+import {
+  CreateUpcomingOpeningsFixture,
+  DEFAULT_UPCOMING_OPENING_META,
+  RemoveUpcomingOpeningsFixture,
+  UpcomingOpeningParams,
+} from '../../fixtures/workingGroups'
 
 import Debugger from 'debug'
 import { FixtureRunner } from '../../Fixture'
 import { workingGroups } from '../../consts'
-import { UpcomingOpeningParams } from '../../fixtures/workingGroups/BaseCreateOpeningFixture'
+import Long from 'long'
+
+const upcomingOpeningsToCreate: UpcomingOpeningParams[] = [
+  // All defaults case:
+  {
+    meta: DEFAULT_UPCOMING_OPENING_META,
+  },
+  // Invalid metadata case:
+  {
+    meta: '0xff',
+    expectMetadataFailure: true,
+  },
+  // Valid metadata edge-cases
+  {
+    meta: {
+      expectedStart: 0,
+      minApplicationStake: Long.fromString('0'),
+      rewardPerBlock: Long.fromString('0'),
+      metadata: {
+        shortDescription: '',
+        description: '',
+        expectedEndingTimestamp: 0,
+        hiringLimit: 0,
+        applicationDetails: '',
+        applicationFormQuestions: [],
+      },
+    },
+  },
+  {
+    meta: {
+      expectedStart: null,
+      minApplicationStake: null,
+      rewardPerBlock: null,
+      metadata: {
+        shortDescription: null,
+        description: null,
+        expectedEndingTimestamp: null,
+        hiringLimit: null,
+        applicationDetails: null,
+        applicationFormQuestions: null,
+      },
+    },
+  },
+  {
+    meta: {},
+  },
+  {
+    meta: {
+      metadata: {
+        hiringLimit: 1,
+        applicationFormQuestions: [{}],
+      },
+    },
+  },
+]
 
 export default async function upcomingOpenings({ api, query, env }: FlowProps): Promise<void> {
   await Promise.all(
@@ -12,34 +71,21 @@ export default async function upcomingOpenings({ api, query, env }: FlowProps):
       const debug = Debugger(`flow:upcoming-openings:${group}`)
       debug('Started')
       api.enableDebugTxLogs()
-
-      const upcomingOpeningsParams: Partial<UpcomingOpeningParams>[] = [
-        // All defaults case:
-        {},
-        // Invalid metadata case:
-        {
-          metadata: '0xff',
-          expectMetadataFailue: true,
-        },
-        // Edge-case valid metadata:
-        {
-          metadata: {
-            shortDescription: '',
-            description: '',
-            expectedEndingTimestamp: 0,
-            hiringLimit: 0,
-            applicationDetails: '',
-            applicationFormQuestions: [],
-          },
-        },
-      ]
-      const createUpcomingOpeningFixture = new CreateUpcomingOpeningsFixture(api, query, group, upcomingOpeningsParams)
+      const createUpcomingOpeningFixture = new CreateUpcomingOpeningsFixture(
+        api,
+        query,
+        group,
+        upcomingOpeningsToCreate
+      )
       await new FixtureRunner(createUpcomingOpeningFixture).runWithQueryNodeChecks()
-      const [createdUpcomingOpeningId] = createUpcomingOpeningFixture.getCreatedUpcomingOpeningIds()
+      const createdUpcomingOpeningIds = createUpcomingOpeningFixture.getCreatedUpcomingOpeningIds()
 
-      const removeUpcomingOpeningFixture = new RemoveUpcomingOpeningsFixture(api, query, group, [
-        createdUpcomingOpeningId,
-      ])
+      const removeUpcomingOpeningFixture = new RemoveUpcomingOpeningsFixture(
+        api,
+        query,
+        group,
+        createdUpcomingOpeningIds
+      )
       await new FixtureRunner(removeUpcomingOpeningFixture).runWithQueryNodeChecks()
 
       debug('Done')

+ 6 - 6
tests/integration-tests/src/graphql/generated/queries.ts

@@ -272,11 +272,11 @@ export type ApplicationFormQuestionFieldsFragment = {
 }
 
 export type OpeningMetadataFieldsFragment = {
-  shortDescription: string
-  description: string
+  shortDescription?: Types.Maybe<string>
+  description?: Types.Maybe<string>
   hiringLimit?: Types.Maybe<number>
   expectedEnding?: Types.Maybe<any>
-  applicationDetails: string
+  applicationDetails?: Types.Maybe<string>
   applicationFormQuestions: Array<ApplicationFormQuestionFieldsFragment>
 }
 
@@ -384,9 +384,9 @@ export type GetWorkingGroupByNameQuery = { workingGroupByUniqueInput?: Types.May
 
 export type UpcomingOpeningFieldsFragment = {
   id: string
-  expectedStart: any
-  stakeAmount: any
-  rewardPerBlock: any
+  expectedStart?: Types.Maybe<any>
+  stakeAmount?: Types.Maybe<any>
+  rewardPerBlock?: Types.Maybe<any>
   createdAt: any
   group: { name: string }
   metadata: OpeningMetadataFieldsFragment

+ 12 - 12
tests/integration-tests/src/graphql/generated/schema.ts

@@ -6396,11 +6396,11 @@ export type UpcomingWorkingGroupOpening = BaseGraphQlObject & {
   group: WorkingGroup
   groupId: Scalars['String']
   /** Expected opening start time */
-  expectedStart: Scalars['DateTime']
+  expectedStart?: Maybe<Scalars['DateTime']>
   /** Expected min. application/role stake amount */
-  stakeAmount: Scalars['BigInt']
+  stakeAmount?: Maybe<Scalars['BigInt']>
   /** Expected reward per block */
-  rewardPerBlock: Scalars['BigInt']
+  rewardPerBlock?: Maybe<Scalars['BigInt']>
   metadata: WorkingGroupOpeningMetadata
   metadataId: Scalars['String']
 }
@@ -6415,9 +6415,9 @@ export type UpcomingWorkingGroupOpeningCreateInput = {
   createdInEventId: Scalars['ID']
   createdAtBlockId: Scalars['ID']
   groupId: Scalars['ID']
-  expectedStart: Scalars['DateTime']
-  stakeAmount: Scalars['BigInt']
-  rewardPerBlock: Scalars['BigInt']
+  expectedStart?: Maybe<Scalars['DateTime']>
+  stakeAmount?: Maybe<Scalars['BigInt']>
+  rewardPerBlock?: Maybe<Scalars['BigInt']>
   metadataId: Scalars['ID']
 }
 
@@ -7908,15 +7908,15 @@ export type WorkingGroupOpeningMetadata = BaseGraphQlObject & {
   /** Whether the originally provided metadata was valid */
   originallyValid: Scalars['Boolean']
   /** Opening short description */
-  shortDescription: Scalars['String']
+  shortDescription?: Maybe<Scalars['String']>
   /** Opening description (md-formatted) */
-  description: Scalars['String']
+  description?: Maybe<Scalars['String']>
   /** Expected max. number of applicants that will be hired */
   hiringLimit?: Maybe<Scalars['Int']>
   /** Expected time when the opening will close */
   expectedEnding?: Maybe<Scalars['DateTime']>
   /** Md-formatted text explaining the application process */
-  applicationDetails: Scalars['String']
+  applicationDetails?: Maybe<Scalars['String']>
   applicationFormQuestions: Array<ApplicationFormQuestion>
   upcomingworkinggroupopeningmetadata?: Maybe<Array<UpcomingWorkingGroupOpening>>
   workinggroupopeningmetadata?: Maybe<Array<WorkingGroupOpening>>
@@ -7930,11 +7930,11 @@ export type WorkingGroupOpeningMetadataConnection = {
 
 export type WorkingGroupOpeningMetadataCreateInput = {
   originallyValid: Scalars['Boolean']
-  shortDescription: Scalars['String']
-  description: Scalars['String']
+  shortDescription?: Maybe<Scalars['String']>
+  description?: Maybe<Scalars['String']>
   hiringLimit?: Maybe<Scalars['Float']>
   expectedEnding?: Maybe<Scalars['DateTime']>
-  applicationDetails: Scalars['String']
+  applicationDetails?: Maybe<Scalars['String']>
 }
 
 export type WorkingGroupOpeningMetadataEdge = {

+ 2 - 2
tests/integration-tests/src/scenarios/workingGroups.ts

@@ -1,5 +1,5 @@
 import leadOpening from '../flows/working-groups/leadOpening'
-import openingAndApplicationStatus from '../flows/working-groups/openingAndApplicationStatus'
+import openingsAndApplications from '../flows/working-groups/openingsAndApplications'
 import upcomingOpenings from '../flows/working-groups/upcomingOpenings'
 import groupStatus from '../flows/working-groups/groupStatus'
 import workerActions from '../flows/working-groups/workerActions'
@@ -8,7 +8,7 @@ import groupBudget from '../flows/working-groups/groupBudget'
 
 scenario(async ({ job }) => {
   const sudoHireLead = job('sudo lead opening', leadOpening)
-  job('opening and application status', openingAndApplicationStatus).requires(sudoHireLead)
+  job('openings and applications', openingsAndApplications).requires(sudoHireLead)
   job('upcoming openings', upcomingOpenings).requires(sudoHireLead)
   job('group status', groupStatus).requires(sudoHireLead)
   job('worker actions', workerActions).requires(sudoHireLead)

+ 2 - 2
tests/integration-tests/src/utils.ts

@@ -7,11 +7,11 @@ import { decodeAddress } from '@polkadot/keyring'
 import { Bytes } from '@polkadot/types'
 import { createType } from '@joystream/types'
 
-type AnyMessage<T> = T & {
+export type AnyMessage<T> = T & {
   toJSON(): Record<string, unknown>
 }
 
-type AnyMetadataClass<T> = {
+export type AnyMetadataClass<T> = {
   decode(binary: Uint8Array): AnyMessage<T>
   encode(obj: T): { finish(): Uint8Array }
   toObject(obj: AnyMessage<T>): Record<string, unknown>