github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/nomad/structs/diff_test.go (about)

     1  package structs
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestJobDiff(t *testing.T) {
    10  	cases := []struct {
    11  		Old, New   *Job
    12  		Expected   *JobDiff
    13  		Error      bool
    14  		Contextual bool
    15  	}{
    16  		{
    17  			Old: nil,
    18  			New: nil,
    19  			Expected: &JobDiff{
    20  				Type: DiffTypeNone,
    21  			},
    22  		},
    23  		{
    24  			// Different IDs
    25  			Old: &Job{
    26  				ID: "foo",
    27  			},
    28  			New: &Job{
    29  				ID: "bar",
    30  			},
    31  			Error: true,
    32  		},
    33  		{
    34  			// Primitive only that is the same
    35  			Old: &Job{
    36  				Region:    "foo",
    37  				ID:        "foo",
    38  				Name:      "foo",
    39  				Type:      "batch",
    40  				Priority:  10,
    41  				AllAtOnce: true,
    42  				Meta: map[string]string{
    43  					"foo": "bar",
    44  				},
    45  			},
    46  			New: &Job{
    47  				Region:    "foo",
    48  				ID:        "foo",
    49  				Name:      "foo",
    50  				Type:      "batch",
    51  				Priority:  10,
    52  				AllAtOnce: true,
    53  				Meta: map[string]string{
    54  					"foo": "bar",
    55  				},
    56  			},
    57  			Expected: &JobDiff{
    58  				Type: DiffTypeNone,
    59  				ID:   "foo",
    60  			},
    61  		},
    62  		{
    63  			// Primitive only that is has diffs
    64  			Old: &Job{
    65  				Region:    "foo",
    66  				ID:        "foo",
    67  				Name:      "foo",
    68  				Type:      "batch",
    69  				Priority:  10,
    70  				AllAtOnce: true,
    71  				Meta: map[string]string{
    72  					"foo": "bar",
    73  				},
    74  			},
    75  			New: &Job{
    76  				Region:    "bar",
    77  				ID:        "foo",
    78  				Name:      "bar",
    79  				Type:      "system",
    80  				Priority:  100,
    81  				AllAtOnce: false,
    82  				Meta: map[string]string{
    83  					"foo": "baz",
    84  				},
    85  			},
    86  			Expected: &JobDiff{
    87  				Type: DiffTypeEdited,
    88  				ID:   "foo",
    89  				Fields: []*FieldDiff{
    90  					{
    91  						Type: DiffTypeEdited,
    92  						Name: "AllAtOnce",
    93  						Old:  "true",
    94  						New:  "false",
    95  					},
    96  					{
    97  						Type: DiffTypeEdited,
    98  						Name: "Meta[foo]",
    99  						Old:  "bar",
   100  						New:  "baz",
   101  					},
   102  					{
   103  						Type: DiffTypeEdited,
   104  						Name: "Name",
   105  						Old:  "foo",
   106  						New:  "bar",
   107  					},
   108  					{
   109  						Type: DiffTypeEdited,
   110  						Name: "Priority",
   111  						Old:  "10",
   112  						New:  "100",
   113  					},
   114  					{
   115  						Type: DiffTypeEdited,
   116  						Name: "Region",
   117  						Old:  "foo",
   118  						New:  "bar",
   119  					},
   120  					{
   121  						Type: DiffTypeEdited,
   122  						Name: "Type",
   123  						Old:  "batch",
   124  						New:  "system",
   125  					},
   126  				},
   127  			},
   128  		},
   129  		{
   130  			// Primitive only deleted job
   131  			Old: &Job{
   132  				Region:    "foo",
   133  				ID:        "foo",
   134  				Name:      "foo",
   135  				Type:      "batch",
   136  				Priority:  10,
   137  				AllAtOnce: true,
   138  				Meta: map[string]string{
   139  					"foo": "bar",
   140  				},
   141  			},
   142  			New: nil,
   143  			Expected: &JobDiff{
   144  				Type: DiffTypeDeleted,
   145  				ID:   "foo",
   146  				Fields: []*FieldDiff{
   147  					{
   148  						Type: DiffTypeDeleted,
   149  						Name: "AllAtOnce",
   150  						Old:  "true",
   151  						New:  "",
   152  					},
   153  					{
   154  						Type: DiffTypeDeleted,
   155  						Name: "Meta[foo]",
   156  						Old:  "bar",
   157  						New:  "",
   158  					},
   159  					{
   160  						Type: DiffTypeDeleted,
   161  						Name: "Name",
   162  						Old:  "foo",
   163  						New:  "",
   164  					},
   165  					{
   166  						Type: DiffTypeDeleted,
   167  						Name: "Priority",
   168  						Old:  "10",
   169  						New:  "",
   170  					},
   171  					{
   172  						Type: DiffTypeDeleted,
   173  						Name: "Region",
   174  						Old:  "foo",
   175  						New:  "",
   176  					},
   177  					{
   178  						Type: DiffTypeDeleted,
   179  						Name: "Stop",
   180  						Old:  "false",
   181  						New:  "",
   182  					},
   183  					{
   184  						Type: DiffTypeDeleted,
   185  						Name: "Type",
   186  						Old:  "batch",
   187  						New:  "",
   188  					},
   189  				},
   190  			},
   191  		},
   192  		{
   193  			// Primitive only added job
   194  			Old: nil,
   195  			New: &Job{
   196  				Region:    "foo",
   197  				ID:        "foo",
   198  				Name:      "foo",
   199  				Type:      "batch",
   200  				Priority:  10,
   201  				AllAtOnce: true,
   202  				Meta: map[string]string{
   203  					"foo": "bar",
   204  				},
   205  			},
   206  			Expected: &JobDiff{
   207  				Type: DiffTypeAdded,
   208  				ID:   "foo",
   209  				Fields: []*FieldDiff{
   210  					{
   211  						Type: DiffTypeAdded,
   212  						Name: "AllAtOnce",
   213  						Old:  "",
   214  						New:  "true",
   215  					},
   216  					{
   217  						Type: DiffTypeAdded,
   218  						Name: "Meta[foo]",
   219  						Old:  "",
   220  						New:  "bar",
   221  					},
   222  					{
   223  						Type: DiffTypeAdded,
   224  						Name: "Name",
   225  						Old:  "",
   226  						New:  "foo",
   227  					},
   228  					{
   229  						Type: DiffTypeAdded,
   230  						Name: "Priority",
   231  						Old:  "",
   232  						New:  "10",
   233  					},
   234  					{
   235  						Type: DiffTypeAdded,
   236  						Name: "Region",
   237  						Old:  "",
   238  						New:  "foo",
   239  					},
   240  					{
   241  						Type: DiffTypeAdded,
   242  						Name: "Stop",
   243  						Old:  "",
   244  						New:  "false",
   245  					},
   246  					{
   247  						Type: DiffTypeAdded,
   248  						Name: "Type",
   249  						Old:  "",
   250  						New:  "batch",
   251  					},
   252  				},
   253  			},
   254  		},
   255  		{
   256  			// Map diff
   257  			Old: &Job{
   258  				Meta: map[string]string{
   259  					"foo": "foo",
   260  					"bar": "bar",
   261  				},
   262  			},
   263  			New: &Job{
   264  				Meta: map[string]string{
   265  					"bar": "bar",
   266  					"baz": "baz",
   267  				},
   268  			},
   269  			Expected: &JobDiff{
   270  				Type: DiffTypeEdited,
   271  				Fields: []*FieldDiff{
   272  					{
   273  						Type: DiffTypeAdded,
   274  						Name: "Meta[baz]",
   275  						Old:  "",
   276  						New:  "baz",
   277  					},
   278  					{
   279  						Type: DiffTypeDeleted,
   280  						Name: "Meta[foo]",
   281  						Old:  "foo",
   282  						New:  "",
   283  					},
   284  				},
   285  			},
   286  		},
   287  		{
   288  			// Datacenter diff both added and removed
   289  			Old: &Job{
   290  				Datacenters: []string{"foo", "bar"},
   291  			},
   292  			New: &Job{
   293  				Datacenters: []string{"baz", "bar"},
   294  			},
   295  			Expected: &JobDiff{
   296  				Type: DiffTypeEdited,
   297  				Objects: []*ObjectDiff{
   298  					{
   299  						Type: DiffTypeEdited,
   300  						Name: "Datacenters",
   301  						Fields: []*FieldDiff{
   302  							{
   303  								Type: DiffTypeAdded,
   304  								Name: "Datacenters",
   305  								Old:  "",
   306  								New:  "baz",
   307  							},
   308  							{
   309  								Type: DiffTypeDeleted,
   310  								Name: "Datacenters",
   311  								Old:  "foo",
   312  								New:  "",
   313  							},
   314  						},
   315  					},
   316  				},
   317  			},
   318  		},
   319  		{
   320  			// Datacenter diff just added
   321  			Old: &Job{
   322  				Datacenters: []string{"foo", "bar"},
   323  			},
   324  			New: &Job{
   325  				Datacenters: []string{"foo", "bar", "baz"},
   326  			},
   327  			Expected: &JobDiff{
   328  				Type: DiffTypeEdited,
   329  				Objects: []*ObjectDiff{
   330  					{
   331  						Type: DiffTypeAdded,
   332  						Name: "Datacenters",
   333  						Fields: []*FieldDiff{
   334  							{
   335  								Type: DiffTypeAdded,
   336  								Name: "Datacenters",
   337  								Old:  "",
   338  								New:  "baz",
   339  							},
   340  						},
   341  					},
   342  				},
   343  			},
   344  		},
   345  		{
   346  			// Datacenter diff just deleted
   347  			Old: &Job{
   348  				Datacenters: []string{"foo", "bar"},
   349  			},
   350  			New: &Job{
   351  				Datacenters: []string{"foo"},
   352  			},
   353  			Expected: &JobDiff{
   354  				Type: DiffTypeEdited,
   355  				Objects: []*ObjectDiff{
   356  					{
   357  						Type: DiffTypeDeleted,
   358  						Name: "Datacenters",
   359  						Fields: []*FieldDiff{
   360  							{
   361  								Type: DiffTypeDeleted,
   362  								Name: "Datacenters",
   363  								Old:  "bar",
   364  								New:  "",
   365  							},
   366  						},
   367  					},
   368  				},
   369  			},
   370  		},
   371  		{
   372  			// Datacenter contextual no change
   373  			Contextual: true,
   374  			Old: &Job{
   375  				Datacenters: []string{"foo", "bar"},
   376  			},
   377  			New: &Job{
   378  				Datacenters: []string{"foo", "bar"},
   379  			},
   380  			Expected: &JobDiff{
   381  				Type: DiffTypeNone,
   382  			},
   383  		},
   384  		{
   385  			// Datacenter contextual
   386  			Contextual: true,
   387  			Old: &Job{
   388  				Datacenters: []string{"foo", "bar"},
   389  			},
   390  			New: &Job{
   391  				Datacenters: []string{"foo", "bar", "baz"},
   392  			},
   393  			Expected: &JobDiff{
   394  				Type: DiffTypeEdited,
   395  				Objects: []*ObjectDiff{
   396  					{
   397  						Type: DiffTypeAdded,
   398  						Name: "Datacenters",
   399  						Fields: []*FieldDiff{
   400  							{
   401  								Type: DiffTypeAdded,
   402  								Name: "Datacenters",
   403  								Old:  "",
   404  								New:  "baz",
   405  							},
   406  							{
   407  								Type: DiffTypeNone,
   408  								Name: "Datacenters",
   409  								Old:  "bar",
   410  								New:  "bar",
   411  							},
   412  							{
   413  								Type: DiffTypeNone,
   414  								Name: "Datacenters",
   415  								Old:  "foo",
   416  								New:  "foo",
   417  							},
   418  						},
   419  					},
   420  				},
   421  			},
   422  		},
   423  		{
   424  			// Periodic added
   425  			Old: &Job{},
   426  			New: &Job{
   427  				Periodic: &PeriodicConfig{
   428  					Enabled:         false,
   429  					Spec:            "*/15 * * * * *",
   430  					SpecType:        "foo",
   431  					ProhibitOverlap: false,
   432  					TimeZone:        "Europe/Minsk",
   433  				},
   434  			},
   435  			Expected: &JobDiff{
   436  				Type: DiffTypeEdited,
   437  				Objects: []*ObjectDiff{
   438  					{
   439  						Type: DiffTypeAdded,
   440  						Name: "Periodic",
   441  						Fields: []*FieldDiff{
   442  							{
   443  								Type: DiffTypeAdded,
   444  								Name: "Enabled",
   445  								Old:  "",
   446  								New:  "false",
   447  							},
   448  							{
   449  								Type: DiffTypeAdded,
   450  								Name: "ProhibitOverlap",
   451  								Old:  "",
   452  								New:  "false",
   453  							},
   454  							{
   455  								Type: DiffTypeAdded,
   456  								Name: "Spec",
   457  								Old:  "",
   458  								New:  "*/15 * * * * *",
   459  							},
   460  							{
   461  								Type: DiffTypeAdded,
   462  								Name: "SpecType",
   463  								Old:  "",
   464  								New:  "foo",
   465  							},
   466  							{
   467  								Type: DiffTypeAdded,
   468  								Name: "TimeZone",
   469  								Old:  "",
   470  								New:  "Europe/Minsk",
   471  							},
   472  						},
   473  					},
   474  				},
   475  			},
   476  		},
   477  		{
   478  			// Periodic deleted
   479  			Old: &Job{
   480  				Periodic: &PeriodicConfig{
   481  					Enabled:         false,
   482  					Spec:            "*/15 * * * * *",
   483  					SpecType:        "foo",
   484  					ProhibitOverlap: false,
   485  					TimeZone:        "Europe/Minsk",
   486  				},
   487  			},
   488  			New: &Job{},
   489  			Expected: &JobDiff{
   490  				Type: DiffTypeEdited,
   491  				Objects: []*ObjectDiff{
   492  					{
   493  						Type: DiffTypeDeleted,
   494  						Name: "Periodic",
   495  						Fields: []*FieldDiff{
   496  							{
   497  								Type: DiffTypeDeleted,
   498  								Name: "Enabled",
   499  								Old:  "false",
   500  								New:  "",
   501  							},
   502  							{
   503  								Type: DiffTypeDeleted,
   504  								Name: "ProhibitOverlap",
   505  								Old:  "false",
   506  								New:  "",
   507  							},
   508  							{
   509  								Type: DiffTypeDeleted,
   510  								Name: "Spec",
   511  								Old:  "*/15 * * * * *",
   512  								New:  "",
   513  							},
   514  							{
   515  								Type: DiffTypeDeleted,
   516  								Name: "SpecType",
   517  								Old:  "foo",
   518  								New:  "",
   519  							},
   520  							{
   521  								Type: DiffTypeDeleted,
   522  								Name: "TimeZone",
   523  								Old:  "Europe/Minsk",
   524  								New:  "",
   525  							},
   526  						},
   527  					},
   528  				},
   529  			},
   530  		},
   531  		{
   532  			// Periodic edited
   533  			Old: &Job{
   534  				Periodic: &PeriodicConfig{
   535  					Enabled:         false,
   536  					Spec:            "*/15 * * * * *",
   537  					SpecType:        "foo",
   538  					ProhibitOverlap: false,
   539  					TimeZone:        "Europe/Minsk",
   540  				},
   541  			},
   542  			New: &Job{
   543  				Periodic: &PeriodicConfig{
   544  					Enabled:         true,
   545  					Spec:            "* * * * * *",
   546  					SpecType:        "cron",
   547  					ProhibitOverlap: true,
   548  					TimeZone:        "America/Los_Angeles",
   549  				},
   550  			},
   551  			Expected: &JobDiff{
   552  				Type: DiffTypeEdited,
   553  				Objects: []*ObjectDiff{
   554  					{
   555  						Type: DiffTypeEdited,
   556  						Name: "Periodic",
   557  						Fields: []*FieldDiff{
   558  							{
   559  								Type: DiffTypeEdited,
   560  								Name: "Enabled",
   561  								Old:  "false",
   562  								New:  "true",
   563  							},
   564  							{
   565  								Type: DiffTypeEdited,
   566  								Name: "ProhibitOverlap",
   567  								Old:  "false",
   568  								New:  "true",
   569  							},
   570  							{
   571  								Type: DiffTypeEdited,
   572  								Name: "Spec",
   573  								Old:  "*/15 * * * * *",
   574  								New:  "* * * * * *",
   575  							},
   576  							{
   577  								Type: DiffTypeEdited,
   578  								Name: "SpecType",
   579  								Old:  "foo",
   580  								New:  "cron",
   581  							},
   582  							{
   583  								Type: DiffTypeEdited,
   584  								Name: "TimeZone",
   585  								Old:  "Europe/Minsk",
   586  								New:  "America/Los_Angeles",
   587  							},
   588  						},
   589  					},
   590  				},
   591  			},
   592  		},
   593  		{
   594  			// Periodic edited with context
   595  			Contextual: true,
   596  			Old: &Job{
   597  				Periodic: &PeriodicConfig{
   598  					Enabled:         false,
   599  					Spec:            "*/15 * * * * *",
   600  					SpecType:        "foo",
   601  					ProhibitOverlap: false,
   602  					TimeZone:        "Europe/Minsk",
   603  				},
   604  			},
   605  			New: &Job{
   606  				Periodic: &PeriodicConfig{
   607  					Enabled:         true,
   608  					Spec:            "* * * * * *",
   609  					SpecType:        "foo",
   610  					ProhibitOverlap: false,
   611  					TimeZone:        "Europe/Minsk",
   612  				},
   613  			},
   614  			Expected: &JobDiff{
   615  				Type: DiffTypeEdited,
   616  				Objects: []*ObjectDiff{
   617  					{
   618  						Type: DiffTypeEdited,
   619  						Name: "Periodic",
   620  						Fields: []*FieldDiff{
   621  							{
   622  								Type: DiffTypeEdited,
   623  								Name: "Enabled",
   624  								Old:  "false",
   625  								New:  "true",
   626  							},
   627  							{
   628  								Type: DiffTypeNone,
   629  								Name: "ProhibitOverlap",
   630  								Old:  "false",
   631  								New:  "false",
   632  							},
   633  							{
   634  								Type: DiffTypeEdited,
   635  								Name: "Spec",
   636  								Old:  "*/15 * * * * *",
   637  								New:  "* * * * * *",
   638  							},
   639  							{
   640  								Type: DiffTypeNone,
   641  								Name: "SpecType",
   642  								Old:  "foo",
   643  								New:  "foo",
   644  							},
   645  							{
   646  								Type: DiffTypeNone,
   647  								Name: "TimeZone",
   648  								Old:  "Europe/Minsk",
   649  								New:  "Europe/Minsk",
   650  							},
   651  						},
   652  					},
   653  				},
   654  			},
   655  		},
   656  		{
   657  			// Constraints edited
   658  			Old: &Job{
   659  				Constraints: []*Constraint{
   660  					{
   661  						LTarget: "foo",
   662  						RTarget: "foo",
   663  						Operand: "foo",
   664  						str:     "foo",
   665  					},
   666  					{
   667  						LTarget: "bar",
   668  						RTarget: "bar",
   669  						Operand: "bar",
   670  						str:     "bar",
   671  					},
   672  				},
   673  			},
   674  			New: &Job{
   675  				Constraints: []*Constraint{
   676  					{
   677  						LTarget: "foo",
   678  						RTarget: "foo",
   679  						Operand: "foo",
   680  						str:     "foo",
   681  					},
   682  					{
   683  						LTarget: "baz",
   684  						RTarget: "baz",
   685  						Operand: "baz",
   686  						str:     "baz",
   687  					},
   688  				},
   689  			},
   690  			Expected: &JobDiff{
   691  				Type: DiffTypeEdited,
   692  				Objects: []*ObjectDiff{
   693  					{
   694  						Type: DiffTypeAdded,
   695  						Name: "Constraint",
   696  						Fields: []*FieldDiff{
   697  							{
   698  								Type: DiffTypeAdded,
   699  								Name: "LTarget",
   700  								Old:  "",
   701  								New:  "baz",
   702  							},
   703  							{
   704  								Type: DiffTypeAdded,
   705  								Name: "Operand",
   706  								Old:  "",
   707  								New:  "baz",
   708  							},
   709  							{
   710  								Type: DiffTypeAdded,
   711  								Name: "RTarget",
   712  								Old:  "",
   713  								New:  "baz",
   714  							},
   715  						},
   716  					},
   717  					{
   718  						Type: DiffTypeDeleted,
   719  						Name: "Constraint",
   720  						Fields: []*FieldDiff{
   721  							{
   722  								Type: DiffTypeDeleted,
   723  								Name: "LTarget",
   724  								Old:  "bar",
   725  								New:  "",
   726  							},
   727  							{
   728  								Type: DiffTypeDeleted,
   729  								Name: "Operand",
   730  								Old:  "bar",
   731  								New:  "",
   732  							},
   733  							{
   734  								Type: DiffTypeDeleted,
   735  								Name: "RTarget",
   736  								Old:  "bar",
   737  								New:  "",
   738  							},
   739  						},
   740  					},
   741  				},
   742  			},
   743  		},
   744  		{
   745  			// Task groups edited
   746  			Old: &Job{
   747  				TaskGroups: []*TaskGroup{
   748  					{
   749  						Name:  "foo",
   750  						Count: 1,
   751  					},
   752  					{
   753  						Name:  "bar",
   754  						Count: 1,
   755  					},
   756  					{
   757  						Name:  "baz",
   758  						Count: 1,
   759  					},
   760  				},
   761  			},
   762  			New: &Job{
   763  				TaskGroups: []*TaskGroup{
   764  					{
   765  						Name:  "bar",
   766  						Count: 1,
   767  					},
   768  					{
   769  						Name:  "baz",
   770  						Count: 2,
   771  					},
   772  					{
   773  						Name:  "bam",
   774  						Count: 1,
   775  					},
   776  				},
   777  			},
   778  			Expected: &JobDiff{
   779  				Type: DiffTypeEdited,
   780  				TaskGroups: []*TaskGroupDiff{
   781  					{
   782  						Type: DiffTypeAdded,
   783  						Name: "bam",
   784  						Fields: []*FieldDiff{
   785  							{
   786  								Type: DiffTypeAdded,
   787  								Name: "Count",
   788  								Old:  "",
   789  								New:  "1",
   790  							},
   791  						},
   792  					},
   793  					{
   794  						Type: DiffTypeNone,
   795  						Name: "bar",
   796  					},
   797  					{
   798  						Type: DiffTypeEdited,
   799  						Name: "baz",
   800  						Fields: []*FieldDiff{
   801  							{
   802  								Type: DiffTypeEdited,
   803  								Name: "Count",
   804  								Old:  "1",
   805  								New:  "2",
   806  							},
   807  						},
   808  					},
   809  					{
   810  						Type: DiffTypeDeleted,
   811  						Name: "foo",
   812  						Fields: []*FieldDiff{
   813  							{
   814  								Type: DiffTypeDeleted,
   815  								Name: "Count",
   816  								Old:  "1",
   817  								New:  "",
   818  							},
   819  						},
   820  					},
   821  				},
   822  			},
   823  		},
   824  		{
   825  			// Parameterized Job added
   826  			Old: &Job{},
   827  			New: &Job{
   828  				ParameterizedJob: &ParameterizedJobConfig{
   829  					Payload:      DispatchPayloadRequired,
   830  					MetaOptional: []string{"foo"},
   831  					MetaRequired: []string{"bar"},
   832  				},
   833  			},
   834  			Expected: &JobDiff{
   835  				Type: DiffTypeEdited,
   836  				Objects: []*ObjectDiff{
   837  					{
   838  						Type: DiffTypeAdded,
   839  						Name: "ParameterizedJob",
   840  						Fields: []*FieldDiff{
   841  							{
   842  								Type: DiffTypeAdded,
   843  								Name: "Payload",
   844  								Old:  "",
   845  								New:  DispatchPayloadRequired,
   846  							},
   847  						},
   848  						Objects: []*ObjectDiff{
   849  							{
   850  								Type: DiffTypeAdded,
   851  								Name: "MetaOptional",
   852  								Fields: []*FieldDiff{
   853  									{
   854  										Type: DiffTypeAdded,
   855  										Name: "MetaOptional",
   856  										Old:  "",
   857  										New:  "foo",
   858  									},
   859  								},
   860  							},
   861  							{
   862  								Type: DiffTypeAdded,
   863  								Name: "MetaRequired",
   864  								Fields: []*FieldDiff{
   865  									{
   866  										Type: DiffTypeAdded,
   867  										Name: "MetaRequired",
   868  										Old:  "",
   869  										New:  "bar",
   870  									},
   871  								},
   872  							},
   873  						},
   874  					},
   875  				},
   876  			},
   877  		},
   878  		{
   879  			// Parameterized Job deleted
   880  			Old: &Job{
   881  				ParameterizedJob: &ParameterizedJobConfig{
   882  					Payload:      DispatchPayloadRequired,
   883  					MetaOptional: []string{"foo"},
   884  					MetaRequired: []string{"bar"},
   885  				},
   886  			},
   887  			New: &Job{},
   888  			Expected: &JobDiff{
   889  				Type: DiffTypeEdited,
   890  				Objects: []*ObjectDiff{
   891  					{
   892  						Type: DiffTypeDeleted,
   893  						Name: "ParameterizedJob",
   894  						Fields: []*FieldDiff{
   895  							{
   896  								Type: DiffTypeDeleted,
   897  								Name: "Payload",
   898  								Old:  DispatchPayloadRequired,
   899  								New:  "",
   900  							},
   901  						},
   902  						Objects: []*ObjectDiff{
   903  							{
   904  								Type: DiffTypeDeleted,
   905  								Name: "MetaOptional",
   906  								Fields: []*FieldDiff{
   907  									{
   908  										Type: DiffTypeDeleted,
   909  										Name: "MetaOptional",
   910  										Old:  "foo",
   911  										New:  "",
   912  									},
   913  								},
   914  							},
   915  							{
   916  								Type: DiffTypeDeleted,
   917  								Name: "MetaRequired",
   918  								Fields: []*FieldDiff{
   919  									{
   920  										Type: DiffTypeDeleted,
   921  										Name: "MetaRequired",
   922  										Old:  "bar",
   923  										New:  "",
   924  									},
   925  								},
   926  							},
   927  						},
   928  					},
   929  				},
   930  			},
   931  		},
   932  		{
   933  			// Parameterized Job edited
   934  			Old: &Job{
   935  				ParameterizedJob: &ParameterizedJobConfig{
   936  					Payload:      DispatchPayloadRequired,
   937  					MetaOptional: []string{"foo"},
   938  					MetaRequired: []string{"bar"},
   939  				},
   940  			},
   941  			New: &Job{
   942  				ParameterizedJob: &ParameterizedJobConfig{
   943  					Payload:      DispatchPayloadOptional,
   944  					MetaOptional: []string{"bam"},
   945  					MetaRequired: []string{"bang"},
   946  				},
   947  			},
   948  			Expected: &JobDiff{
   949  				Type: DiffTypeEdited,
   950  				Objects: []*ObjectDiff{
   951  					{
   952  						Type: DiffTypeEdited,
   953  						Name: "ParameterizedJob",
   954  						Fields: []*FieldDiff{
   955  							{
   956  								Type: DiffTypeEdited,
   957  								Name: "Payload",
   958  								Old:  DispatchPayloadRequired,
   959  								New:  DispatchPayloadOptional,
   960  							},
   961  						},
   962  						Objects: []*ObjectDiff{
   963  							{
   964  								Type: DiffTypeEdited,
   965  								Name: "MetaOptional",
   966  								Fields: []*FieldDiff{
   967  									{
   968  										Type: DiffTypeAdded,
   969  										Name: "MetaOptional",
   970  										Old:  "",
   971  										New:  "bam",
   972  									},
   973  									{
   974  										Type: DiffTypeDeleted,
   975  										Name: "MetaOptional",
   976  										Old:  "foo",
   977  										New:  "",
   978  									},
   979  								},
   980  							},
   981  							{
   982  								Type: DiffTypeEdited,
   983  								Name: "MetaRequired",
   984  								Fields: []*FieldDiff{
   985  									{
   986  										Type: DiffTypeAdded,
   987  										Name: "MetaRequired",
   988  										Old:  "",
   989  										New:  "bang",
   990  									},
   991  									{
   992  										Type: DiffTypeDeleted,
   993  										Name: "MetaRequired",
   994  										Old:  "bar",
   995  										New:  "",
   996  									},
   997  								},
   998  							},
   999  						},
  1000  					},
  1001  				},
  1002  			},
  1003  		},
  1004  		{
  1005  			// Parameterized Job edited with context
  1006  			Contextual: true,
  1007  			Old: &Job{
  1008  				ParameterizedJob: &ParameterizedJobConfig{
  1009  					Payload:      DispatchPayloadRequired,
  1010  					MetaOptional: []string{"foo"},
  1011  					MetaRequired: []string{"bar"},
  1012  				},
  1013  			},
  1014  			New: &Job{
  1015  				ParameterizedJob: &ParameterizedJobConfig{
  1016  					Payload:      DispatchPayloadOptional,
  1017  					MetaOptional: []string{"foo"},
  1018  					MetaRequired: []string{"bar"},
  1019  				},
  1020  			},
  1021  			Expected: &JobDiff{
  1022  				Type: DiffTypeEdited,
  1023  				Objects: []*ObjectDiff{
  1024  					{
  1025  						Type: DiffTypeEdited,
  1026  						Name: "ParameterizedJob",
  1027  						Fields: []*FieldDiff{
  1028  							{
  1029  								Type: DiffTypeEdited,
  1030  								Name: "Payload",
  1031  								Old:  DispatchPayloadRequired,
  1032  								New:  DispatchPayloadOptional,
  1033  							},
  1034  						},
  1035  						Objects: []*ObjectDiff{
  1036  							{
  1037  								Type: DiffTypeNone,
  1038  								Name: "MetaOptional",
  1039  								Fields: []*FieldDiff{
  1040  									{
  1041  										Type: DiffTypeNone,
  1042  										Name: "MetaOptional",
  1043  										Old:  "foo",
  1044  										New:  "foo",
  1045  									},
  1046  								},
  1047  							},
  1048  							{
  1049  								Type: DiffTypeNone,
  1050  								Name: "MetaRequired",
  1051  								Fields: []*FieldDiff{
  1052  									{
  1053  										Type: DiffTypeNone,
  1054  										Name: "MetaRequired",
  1055  										Old:  "bar",
  1056  										New:  "bar",
  1057  									},
  1058  								},
  1059  							},
  1060  						},
  1061  					},
  1062  				},
  1063  			},
  1064  		},
  1065  	}
  1066  
  1067  	for i, c := range cases {
  1068  		actual, err := c.Old.Diff(c.New, c.Contextual)
  1069  		if c.Error && err == nil {
  1070  			t.Fatalf("case %d: expected errored", i+1)
  1071  		} else if err != nil {
  1072  			if !c.Error {
  1073  				t.Fatalf("case %d: errored %#v", i+1, err)
  1074  			} else {
  1075  				continue
  1076  			}
  1077  		}
  1078  
  1079  		if !reflect.DeepEqual(actual, c.Expected) {
  1080  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  1081  				i+1, actual, c.Expected)
  1082  		}
  1083  	}
  1084  }
  1085  
  1086  func TestTaskGroupDiff(t *testing.T) {
  1087  	cases := []struct {
  1088  		Old, New   *TaskGroup
  1089  		Expected   *TaskGroupDiff
  1090  		Error      bool
  1091  		Contextual bool
  1092  	}{
  1093  		{
  1094  			Old: nil,
  1095  			New: nil,
  1096  			Expected: &TaskGroupDiff{
  1097  				Type: DiffTypeNone,
  1098  			},
  1099  		},
  1100  		{
  1101  			// Primitive only that has different names
  1102  			Old: &TaskGroup{
  1103  				Name:  "foo",
  1104  				Count: 10,
  1105  				Meta: map[string]string{
  1106  					"foo": "bar",
  1107  				},
  1108  			},
  1109  			New: &TaskGroup{
  1110  				Name:  "bar",
  1111  				Count: 10,
  1112  				Meta: map[string]string{
  1113  					"foo": "bar",
  1114  				},
  1115  			},
  1116  			Error: true,
  1117  		},
  1118  		{
  1119  			// Primitive only that is the same
  1120  			Old: &TaskGroup{
  1121  				Name:  "foo",
  1122  				Count: 10,
  1123  				Meta: map[string]string{
  1124  					"foo": "bar",
  1125  				},
  1126  			},
  1127  			New: &TaskGroup{
  1128  				Name:  "foo",
  1129  				Count: 10,
  1130  				Meta: map[string]string{
  1131  					"foo": "bar",
  1132  				},
  1133  			},
  1134  			Expected: &TaskGroupDiff{
  1135  				Type: DiffTypeNone,
  1136  				Name: "foo",
  1137  			},
  1138  		},
  1139  		{
  1140  			// Primitive only that has diffs
  1141  			Old: &TaskGroup{
  1142  				Name:  "foo",
  1143  				Count: 10,
  1144  				Meta: map[string]string{
  1145  					"foo": "bar",
  1146  				},
  1147  			},
  1148  			New: &TaskGroup{
  1149  				Name:  "foo",
  1150  				Count: 100,
  1151  				Meta: map[string]string{
  1152  					"foo": "baz",
  1153  				},
  1154  			},
  1155  			Expected: &TaskGroupDiff{
  1156  				Type: DiffTypeEdited,
  1157  				Name: "foo",
  1158  				Fields: []*FieldDiff{
  1159  					{
  1160  						Type: DiffTypeEdited,
  1161  						Name: "Count",
  1162  						Old:  "10",
  1163  						New:  "100",
  1164  					},
  1165  					{
  1166  						Type: DiffTypeEdited,
  1167  						Name: "Meta[foo]",
  1168  						Old:  "bar",
  1169  						New:  "baz",
  1170  					},
  1171  				},
  1172  			},
  1173  		},
  1174  		{
  1175  			// Map diff
  1176  			Old: &TaskGroup{
  1177  				Meta: map[string]string{
  1178  					"foo": "foo",
  1179  					"bar": "bar",
  1180  				},
  1181  			},
  1182  			New: &TaskGroup{
  1183  				Meta: map[string]string{
  1184  					"bar": "bar",
  1185  					"baz": "baz",
  1186  				},
  1187  			},
  1188  			Expected: &TaskGroupDiff{
  1189  				Type: DiffTypeEdited,
  1190  				Fields: []*FieldDiff{
  1191  					{
  1192  						Type: DiffTypeAdded,
  1193  						Name: "Meta[baz]",
  1194  						Old:  "",
  1195  						New:  "baz",
  1196  					},
  1197  					{
  1198  						Type: DiffTypeDeleted,
  1199  						Name: "Meta[foo]",
  1200  						Old:  "foo",
  1201  						New:  "",
  1202  					},
  1203  				},
  1204  			},
  1205  		},
  1206  		{
  1207  			// Constraints edited
  1208  			Old: &TaskGroup{
  1209  				Constraints: []*Constraint{
  1210  					{
  1211  						LTarget: "foo",
  1212  						RTarget: "foo",
  1213  						Operand: "foo",
  1214  						str:     "foo",
  1215  					},
  1216  					{
  1217  						LTarget: "bar",
  1218  						RTarget: "bar",
  1219  						Operand: "bar",
  1220  						str:     "bar",
  1221  					},
  1222  				},
  1223  			},
  1224  			New: &TaskGroup{
  1225  				Constraints: []*Constraint{
  1226  					{
  1227  						LTarget: "foo",
  1228  						RTarget: "foo",
  1229  						Operand: "foo",
  1230  						str:     "foo",
  1231  					},
  1232  					{
  1233  						LTarget: "baz",
  1234  						RTarget: "baz",
  1235  						Operand: "baz",
  1236  						str:     "baz",
  1237  					},
  1238  				},
  1239  			},
  1240  			Expected: &TaskGroupDiff{
  1241  				Type: DiffTypeEdited,
  1242  				Objects: []*ObjectDiff{
  1243  					{
  1244  						Type: DiffTypeAdded,
  1245  						Name: "Constraint",
  1246  						Fields: []*FieldDiff{
  1247  							{
  1248  								Type: DiffTypeAdded,
  1249  								Name: "LTarget",
  1250  								Old:  "",
  1251  								New:  "baz",
  1252  							},
  1253  							{
  1254  								Type: DiffTypeAdded,
  1255  								Name: "Operand",
  1256  								Old:  "",
  1257  								New:  "baz",
  1258  							},
  1259  							{
  1260  								Type: DiffTypeAdded,
  1261  								Name: "RTarget",
  1262  								Old:  "",
  1263  								New:  "baz",
  1264  							},
  1265  						},
  1266  					},
  1267  					{
  1268  						Type: DiffTypeDeleted,
  1269  						Name: "Constraint",
  1270  						Fields: []*FieldDiff{
  1271  							{
  1272  								Type: DiffTypeDeleted,
  1273  								Name: "LTarget",
  1274  								Old:  "bar",
  1275  								New:  "",
  1276  							},
  1277  							{
  1278  								Type: DiffTypeDeleted,
  1279  								Name: "Operand",
  1280  								Old:  "bar",
  1281  								New:  "",
  1282  							},
  1283  							{
  1284  								Type: DiffTypeDeleted,
  1285  								Name: "RTarget",
  1286  								Old:  "bar",
  1287  								New:  "",
  1288  							},
  1289  						},
  1290  					},
  1291  				},
  1292  			},
  1293  		},
  1294  		{
  1295  			// RestartPolicy added
  1296  			Old: &TaskGroup{},
  1297  			New: &TaskGroup{
  1298  				RestartPolicy: &RestartPolicy{
  1299  					Attempts: 1,
  1300  					Interval: 1 * time.Second,
  1301  					Delay:    1 * time.Second,
  1302  					Mode:     "fail",
  1303  				},
  1304  			},
  1305  			Expected: &TaskGroupDiff{
  1306  				Type: DiffTypeEdited,
  1307  				Objects: []*ObjectDiff{
  1308  					{
  1309  						Type: DiffTypeAdded,
  1310  						Name: "RestartPolicy",
  1311  						Fields: []*FieldDiff{
  1312  							{
  1313  								Type: DiffTypeAdded,
  1314  								Name: "Attempts",
  1315  								Old:  "",
  1316  								New:  "1",
  1317  							},
  1318  							{
  1319  								Type: DiffTypeAdded,
  1320  								Name: "Delay",
  1321  								Old:  "",
  1322  								New:  "1000000000",
  1323  							},
  1324  							{
  1325  								Type: DiffTypeAdded,
  1326  								Name: "Interval",
  1327  								Old:  "",
  1328  								New:  "1000000000",
  1329  							},
  1330  							{
  1331  								Type: DiffTypeAdded,
  1332  								Name: "Mode",
  1333  								Old:  "",
  1334  								New:  "fail",
  1335  							},
  1336  						},
  1337  					},
  1338  				},
  1339  			},
  1340  		},
  1341  		{
  1342  			// RestartPolicy deleted
  1343  			Old: &TaskGroup{
  1344  				RestartPolicy: &RestartPolicy{
  1345  					Attempts: 1,
  1346  					Interval: 1 * time.Second,
  1347  					Delay:    1 * time.Second,
  1348  					Mode:     "fail",
  1349  				},
  1350  			},
  1351  			New: &TaskGroup{},
  1352  			Expected: &TaskGroupDiff{
  1353  				Type: DiffTypeEdited,
  1354  				Objects: []*ObjectDiff{
  1355  					{
  1356  						Type: DiffTypeDeleted,
  1357  						Name: "RestartPolicy",
  1358  						Fields: []*FieldDiff{
  1359  							{
  1360  								Type: DiffTypeDeleted,
  1361  								Name: "Attempts",
  1362  								Old:  "1",
  1363  								New:  "",
  1364  							},
  1365  							{
  1366  								Type: DiffTypeDeleted,
  1367  								Name: "Delay",
  1368  								Old:  "1000000000",
  1369  								New:  "",
  1370  							},
  1371  							{
  1372  								Type: DiffTypeDeleted,
  1373  								Name: "Interval",
  1374  								Old:  "1000000000",
  1375  								New:  "",
  1376  							},
  1377  							{
  1378  								Type: DiffTypeDeleted,
  1379  								Name: "Mode",
  1380  								Old:  "fail",
  1381  								New:  "",
  1382  							},
  1383  						},
  1384  					},
  1385  				},
  1386  			},
  1387  		},
  1388  		{
  1389  			// RestartPolicy edited
  1390  			Old: &TaskGroup{
  1391  				RestartPolicy: &RestartPolicy{
  1392  					Attempts: 1,
  1393  					Interval: 1 * time.Second,
  1394  					Delay:    1 * time.Second,
  1395  					Mode:     "fail",
  1396  				},
  1397  			},
  1398  			New: &TaskGroup{
  1399  				RestartPolicy: &RestartPolicy{
  1400  					Attempts: 2,
  1401  					Interval: 2 * time.Second,
  1402  					Delay:    2 * time.Second,
  1403  					Mode:     "delay",
  1404  				},
  1405  			},
  1406  			Expected: &TaskGroupDiff{
  1407  				Type: DiffTypeEdited,
  1408  				Objects: []*ObjectDiff{
  1409  					{
  1410  						Type: DiffTypeEdited,
  1411  						Name: "RestartPolicy",
  1412  						Fields: []*FieldDiff{
  1413  							{
  1414  								Type: DiffTypeEdited,
  1415  								Name: "Attempts",
  1416  								Old:  "1",
  1417  								New:  "2",
  1418  							},
  1419  							{
  1420  								Type: DiffTypeEdited,
  1421  								Name: "Delay",
  1422  								Old:  "1000000000",
  1423  								New:  "2000000000",
  1424  							},
  1425  							{
  1426  								Type: DiffTypeEdited,
  1427  								Name: "Interval",
  1428  								Old:  "1000000000",
  1429  								New:  "2000000000",
  1430  							},
  1431  							{
  1432  								Type: DiffTypeEdited,
  1433  								Name: "Mode",
  1434  								Old:  "fail",
  1435  								New:  "delay",
  1436  							},
  1437  						},
  1438  					},
  1439  				},
  1440  			},
  1441  		},
  1442  		{
  1443  			// RestartPolicy edited with context
  1444  			Contextual: true,
  1445  			Old: &TaskGroup{
  1446  				RestartPolicy: &RestartPolicy{
  1447  					Attempts: 1,
  1448  					Interval: 1 * time.Second,
  1449  					Delay:    1 * time.Second,
  1450  					Mode:     "fail",
  1451  				},
  1452  			},
  1453  			New: &TaskGroup{
  1454  				RestartPolicy: &RestartPolicy{
  1455  					Attempts: 2,
  1456  					Interval: 2 * time.Second,
  1457  					Delay:    1 * time.Second,
  1458  					Mode:     "fail",
  1459  				},
  1460  			},
  1461  			Expected: &TaskGroupDiff{
  1462  				Type: DiffTypeEdited,
  1463  				Objects: []*ObjectDiff{
  1464  					{
  1465  						Type: DiffTypeEdited,
  1466  						Name: "RestartPolicy",
  1467  						Fields: []*FieldDiff{
  1468  							{
  1469  								Type: DiffTypeEdited,
  1470  								Name: "Attempts",
  1471  								Old:  "1",
  1472  								New:  "2",
  1473  							},
  1474  							{
  1475  								Type: DiffTypeNone,
  1476  								Name: "Delay",
  1477  								Old:  "1000000000",
  1478  								New:  "1000000000",
  1479  							},
  1480  							{
  1481  								Type: DiffTypeEdited,
  1482  								Name: "Interval",
  1483  								Old:  "1000000000",
  1484  								New:  "2000000000",
  1485  							},
  1486  							{
  1487  								Type: DiffTypeNone,
  1488  								Name: "Mode",
  1489  								Old:  "fail",
  1490  								New:  "fail",
  1491  							},
  1492  						},
  1493  					},
  1494  				},
  1495  			},
  1496  		},
  1497  		{
  1498  			// ReschedulePolicy added
  1499  			Old: &TaskGroup{},
  1500  			New: &TaskGroup{
  1501  				ReschedulePolicy: &ReschedulePolicy{
  1502  					Attempts: 1,
  1503  					Interval: 15 * time.Second,
  1504  				},
  1505  			},
  1506  			Expected: &TaskGroupDiff{
  1507  				Type: DiffTypeEdited,
  1508  				Objects: []*ObjectDiff{
  1509  					{
  1510  						Type: DiffTypeAdded,
  1511  						Name: "ReschedulePolicy",
  1512  						Fields: []*FieldDiff{
  1513  							{
  1514  								Type: DiffTypeAdded,
  1515  								Name: "Attempts",
  1516  								Old:  "",
  1517  								New:  "1",
  1518  							},
  1519  							{
  1520  								Type: DiffTypeAdded,
  1521  								Name: "Interval",
  1522  								Old:  "",
  1523  								New:  "15000000000",
  1524  							},
  1525  						},
  1526  					},
  1527  				},
  1528  			},
  1529  		},
  1530  		{
  1531  			// ReschedulePolicy deleted
  1532  			Old: &TaskGroup{
  1533  				ReschedulePolicy: &ReschedulePolicy{
  1534  					Attempts: 1,
  1535  					Interval: 15 * time.Second,
  1536  				},
  1537  			},
  1538  			New: &TaskGroup{},
  1539  			Expected: &TaskGroupDiff{
  1540  				Type: DiffTypeEdited,
  1541  				Objects: []*ObjectDiff{
  1542  					{
  1543  						Type: DiffTypeDeleted,
  1544  						Name: "ReschedulePolicy",
  1545  						Fields: []*FieldDiff{
  1546  							{
  1547  								Type: DiffTypeDeleted,
  1548  								Name: "Attempts",
  1549  								Old:  "1",
  1550  								New:  "",
  1551  							},
  1552  							{
  1553  								Type: DiffTypeDeleted,
  1554  								Name: "Interval",
  1555  								Old:  "15000000000",
  1556  								New:  "",
  1557  							},
  1558  						},
  1559  					},
  1560  				},
  1561  			},
  1562  		},
  1563  		{
  1564  			// ReschedulePolicy edited
  1565  			Old: &TaskGroup{
  1566  				ReschedulePolicy: &ReschedulePolicy{
  1567  					Attempts: 1,
  1568  					Interval: 1 * time.Second,
  1569  				},
  1570  			},
  1571  			New: &TaskGroup{
  1572  				ReschedulePolicy: &ReschedulePolicy{
  1573  					Attempts: 2,
  1574  					Interval: 2 * time.Second,
  1575  				},
  1576  			},
  1577  			Expected: &TaskGroupDiff{
  1578  				Type: DiffTypeEdited,
  1579  				Objects: []*ObjectDiff{
  1580  					{
  1581  						Type: DiffTypeEdited,
  1582  						Name: "ReschedulePolicy",
  1583  						Fields: []*FieldDiff{
  1584  							{
  1585  								Type: DiffTypeEdited,
  1586  								Name: "Attempts",
  1587  								Old:  "1",
  1588  								New:  "2",
  1589  							},
  1590  							{
  1591  								Type: DiffTypeEdited,
  1592  								Name: "Interval",
  1593  								Old:  "1000000000",
  1594  								New:  "2000000000",
  1595  							},
  1596  						},
  1597  					},
  1598  				},
  1599  			},
  1600  		}, {
  1601  			// ReschedulePolicy edited with context
  1602  			Contextual: true,
  1603  			Old: &TaskGroup{
  1604  				ReschedulePolicy: &ReschedulePolicy{
  1605  					Attempts: 1,
  1606  					Interval: 1 * time.Second,
  1607  				},
  1608  			},
  1609  			New: &TaskGroup{
  1610  				ReschedulePolicy: &ReschedulePolicy{
  1611  					Attempts: 1,
  1612  					Interval: 2 * time.Second,
  1613  				},
  1614  			},
  1615  			Expected: &TaskGroupDiff{
  1616  				Type: DiffTypeEdited,
  1617  				Objects: []*ObjectDiff{
  1618  					{
  1619  						Type: DiffTypeEdited,
  1620  						Name: "ReschedulePolicy",
  1621  						Fields: []*FieldDiff{
  1622  							{
  1623  								Type: DiffTypeNone,
  1624  								Name: "Attempts",
  1625  								Old:  "1",
  1626  								New:  "1",
  1627  							},
  1628  							{
  1629  								Type: DiffTypeEdited,
  1630  								Name: "Interval",
  1631  								Old:  "1000000000",
  1632  								New:  "2000000000",
  1633  							},
  1634  						},
  1635  					},
  1636  				},
  1637  			},
  1638  		},
  1639  		{
  1640  			// Update strategy deleted
  1641  			Old: &TaskGroup{
  1642  				Update: &UpdateStrategy{
  1643  					AutoRevert: true,
  1644  				},
  1645  			},
  1646  			New: &TaskGroup{},
  1647  			Expected: &TaskGroupDiff{
  1648  				Type: DiffTypeEdited,
  1649  				Objects: []*ObjectDiff{
  1650  					{
  1651  						Type: DiffTypeDeleted,
  1652  						Name: "Update",
  1653  						Fields: []*FieldDiff{
  1654  							{
  1655  								Type: DiffTypeDeleted,
  1656  								Name: "AutoRevert",
  1657  								Old:  "true",
  1658  								New:  "",
  1659  							},
  1660  							{
  1661  								Type: DiffTypeDeleted,
  1662  								Name: "Canary",
  1663  								Old:  "0",
  1664  								New:  "",
  1665  							},
  1666  							{
  1667  								Type: DiffTypeDeleted,
  1668  								Name: "HealthyDeadline",
  1669  								Old:  "0",
  1670  								New:  "",
  1671  							},
  1672  							{
  1673  								Type: DiffTypeDeleted,
  1674  								Name: "MaxParallel",
  1675  								Old:  "0",
  1676  								New:  "",
  1677  							},
  1678  							{
  1679  								Type: DiffTypeDeleted,
  1680  								Name: "MinHealthyTime",
  1681  								Old:  "0",
  1682  								New:  "",
  1683  							},
  1684  						},
  1685  					},
  1686  				},
  1687  			},
  1688  		},
  1689  		{
  1690  			// Update strategy added
  1691  			Old: &TaskGroup{},
  1692  			New: &TaskGroup{
  1693  				Update: &UpdateStrategy{
  1694  					AutoRevert: true,
  1695  				},
  1696  			},
  1697  			Expected: &TaskGroupDiff{
  1698  				Type: DiffTypeEdited,
  1699  				Objects: []*ObjectDiff{
  1700  					{
  1701  						Type: DiffTypeAdded,
  1702  						Name: "Update",
  1703  						Fields: []*FieldDiff{
  1704  							{
  1705  								Type: DiffTypeAdded,
  1706  								Name: "AutoRevert",
  1707  								Old:  "",
  1708  								New:  "true",
  1709  							},
  1710  							{
  1711  								Type: DiffTypeAdded,
  1712  								Name: "Canary",
  1713  								Old:  "",
  1714  								New:  "0",
  1715  							},
  1716  							{
  1717  								Type: DiffTypeAdded,
  1718  								Name: "HealthyDeadline",
  1719  								Old:  "",
  1720  								New:  "0",
  1721  							},
  1722  							{
  1723  								Type: DiffTypeAdded,
  1724  								Name: "MaxParallel",
  1725  								Old:  "",
  1726  								New:  "0",
  1727  							},
  1728  							{
  1729  								Type: DiffTypeAdded,
  1730  								Name: "MinHealthyTime",
  1731  								Old:  "",
  1732  								New:  "0",
  1733  							},
  1734  						},
  1735  					},
  1736  				},
  1737  			},
  1738  		},
  1739  		{
  1740  			// Update strategy edited
  1741  			Old: &TaskGroup{
  1742  				Update: &UpdateStrategy{
  1743  					MaxParallel:     5,
  1744  					HealthCheck:     "foo",
  1745  					MinHealthyTime:  1 * time.Second,
  1746  					HealthyDeadline: 30 * time.Second,
  1747  					AutoRevert:      true,
  1748  					Canary:          2,
  1749  				},
  1750  			},
  1751  			New: &TaskGroup{
  1752  				Update: &UpdateStrategy{
  1753  					MaxParallel:     7,
  1754  					HealthCheck:     "bar",
  1755  					MinHealthyTime:  2 * time.Second,
  1756  					HealthyDeadline: 31 * time.Second,
  1757  					AutoRevert:      false,
  1758  					Canary:          1,
  1759  				},
  1760  			},
  1761  			Expected: &TaskGroupDiff{
  1762  				Type: DiffTypeEdited,
  1763  				Objects: []*ObjectDiff{
  1764  					{
  1765  						Type: DiffTypeEdited,
  1766  						Name: "Update",
  1767  						Fields: []*FieldDiff{
  1768  							{
  1769  								Type: DiffTypeEdited,
  1770  								Name: "AutoRevert",
  1771  								Old:  "true",
  1772  								New:  "false",
  1773  							},
  1774  							{
  1775  								Type: DiffTypeEdited,
  1776  								Name: "Canary",
  1777  								Old:  "2",
  1778  								New:  "1",
  1779  							},
  1780  							{
  1781  								Type: DiffTypeEdited,
  1782  								Name: "HealthCheck",
  1783  								Old:  "foo",
  1784  								New:  "bar",
  1785  							},
  1786  							{
  1787  								Type: DiffTypeEdited,
  1788  								Name: "HealthyDeadline",
  1789  								Old:  "30000000000",
  1790  								New:  "31000000000",
  1791  							},
  1792  							{
  1793  								Type: DiffTypeEdited,
  1794  								Name: "MaxParallel",
  1795  								Old:  "5",
  1796  								New:  "7",
  1797  							},
  1798  							{
  1799  								Type: DiffTypeEdited,
  1800  								Name: "MinHealthyTime",
  1801  								Old:  "1000000000",
  1802  								New:  "2000000000",
  1803  							},
  1804  						},
  1805  					},
  1806  				},
  1807  			},
  1808  		},
  1809  		{
  1810  			// Update strategy edited with context
  1811  			Contextual: true,
  1812  			Old: &TaskGroup{
  1813  				Update: &UpdateStrategy{
  1814  					MaxParallel:     5,
  1815  					HealthCheck:     "foo",
  1816  					MinHealthyTime:  1 * time.Second,
  1817  					HealthyDeadline: 30 * time.Second,
  1818  					AutoRevert:      true,
  1819  					Canary:          2,
  1820  				},
  1821  			},
  1822  			New: &TaskGroup{
  1823  				Update: &UpdateStrategy{
  1824  					MaxParallel:     7,
  1825  					HealthCheck:     "foo",
  1826  					MinHealthyTime:  1 * time.Second,
  1827  					HealthyDeadline: 30 * time.Second,
  1828  					AutoRevert:      true,
  1829  					Canary:          2,
  1830  				},
  1831  			},
  1832  			Expected: &TaskGroupDiff{
  1833  				Type: DiffTypeEdited,
  1834  				Objects: []*ObjectDiff{
  1835  					{
  1836  						Type: DiffTypeEdited,
  1837  						Name: "Update",
  1838  						Fields: []*FieldDiff{
  1839  							{
  1840  								Type: DiffTypeNone,
  1841  								Name: "AutoRevert",
  1842  								Old:  "true",
  1843  								New:  "true",
  1844  							},
  1845  							{
  1846  								Type: DiffTypeNone,
  1847  								Name: "Canary",
  1848  								Old:  "2",
  1849  								New:  "2",
  1850  							},
  1851  							{
  1852  								Type: DiffTypeNone,
  1853  								Name: "HealthCheck",
  1854  								Old:  "foo",
  1855  								New:  "foo",
  1856  							},
  1857  							{
  1858  								Type: DiffTypeNone,
  1859  								Name: "HealthyDeadline",
  1860  								Old:  "30000000000",
  1861  								New:  "30000000000",
  1862  							},
  1863  							{
  1864  								Type: DiffTypeEdited,
  1865  								Name: "MaxParallel",
  1866  								Old:  "5",
  1867  								New:  "7",
  1868  							},
  1869  							{
  1870  								Type: DiffTypeNone,
  1871  								Name: "MinHealthyTime",
  1872  								Old:  "1000000000",
  1873  								New:  "1000000000",
  1874  							},
  1875  						},
  1876  					},
  1877  				},
  1878  			},
  1879  		},
  1880  		{
  1881  			// EphemeralDisk added
  1882  			Old: &TaskGroup{},
  1883  			New: &TaskGroup{
  1884  				EphemeralDisk: &EphemeralDisk{
  1885  					Migrate: true,
  1886  					Sticky:  true,
  1887  					SizeMB:  100,
  1888  				},
  1889  			},
  1890  			Expected: &TaskGroupDiff{
  1891  				Type: DiffTypeEdited,
  1892  				Objects: []*ObjectDiff{
  1893  					{
  1894  						Type: DiffTypeAdded,
  1895  						Name: "EphemeralDisk",
  1896  						Fields: []*FieldDiff{
  1897  							{
  1898  								Type: DiffTypeAdded,
  1899  								Name: "Migrate",
  1900  								Old:  "",
  1901  								New:  "true",
  1902  							},
  1903  							{
  1904  								Type: DiffTypeAdded,
  1905  								Name: "SizeMB",
  1906  								Old:  "",
  1907  								New:  "100",
  1908  							},
  1909  							{
  1910  								Type: DiffTypeAdded,
  1911  								Name: "Sticky",
  1912  								Old:  "",
  1913  								New:  "true",
  1914  							},
  1915  						},
  1916  					},
  1917  				},
  1918  			},
  1919  		},
  1920  		{
  1921  			// EphemeralDisk deleted
  1922  			Old: &TaskGroup{
  1923  				EphemeralDisk: &EphemeralDisk{
  1924  					Migrate: true,
  1925  					Sticky:  true,
  1926  					SizeMB:  100,
  1927  				},
  1928  			},
  1929  			New: &TaskGroup{},
  1930  			Expected: &TaskGroupDiff{
  1931  				Type: DiffTypeEdited,
  1932  				Objects: []*ObjectDiff{
  1933  					{
  1934  						Type: DiffTypeDeleted,
  1935  						Name: "EphemeralDisk",
  1936  						Fields: []*FieldDiff{
  1937  							{
  1938  								Type: DiffTypeDeleted,
  1939  								Name: "Migrate",
  1940  								Old:  "true",
  1941  								New:  "",
  1942  							},
  1943  							{
  1944  								Type: DiffTypeDeleted,
  1945  								Name: "SizeMB",
  1946  								Old:  "100",
  1947  								New:  "",
  1948  							},
  1949  							{
  1950  								Type: DiffTypeDeleted,
  1951  								Name: "Sticky",
  1952  								Old:  "true",
  1953  								New:  "",
  1954  							},
  1955  						},
  1956  					},
  1957  				},
  1958  			},
  1959  		},
  1960  		{
  1961  			// EphemeralDisk edited
  1962  			Old: &TaskGroup{
  1963  				EphemeralDisk: &EphemeralDisk{
  1964  					Migrate: true,
  1965  					Sticky:  true,
  1966  					SizeMB:  150,
  1967  				},
  1968  			},
  1969  			New: &TaskGroup{
  1970  				EphemeralDisk: &EphemeralDisk{
  1971  					Migrate: false,
  1972  					Sticky:  false,
  1973  					SizeMB:  90,
  1974  				},
  1975  			},
  1976  			Expected: &TaskGroupDiff{
  1977  				Type: DiffTypeEdited,
  1978  				Objects: []*ObjectDiff{
  1979  					{
  1980  						Type: DiffTypeEdited,
  1981  						Name: "EphemeralDisk",
  1982  						Fields: []*FieldDiff{
  1983  							{
  1984  								Type: DiffTypeEdited,
  1985  								Name: "Migrate",
  1986  								Old:  "true",
  1987  								New:  "false",
  1988  							},
  1989  							{
  1990  								Type: DiffTypeEdited,
  1991  								Name: "SizeMB",
  1992  								Old:  "150",
  1993  								New:  "90",
  1994  							},
  1995  
  1996  							{
  1997  								Type: DiffTypeEdited,
  1998  								Name: "Sticky",
  1999  								Old:  "true",
  2000  								New:  "false",
  2001  							},
  2002  						},
  2003  					},
  2004  				},
  2005  			},
  2006  		},
  2007  		{
  2008  			// EphemeralDisk edited with context
  2009  			Contextual: true,
  2010  			Old: &TaskGroup{
  2011  				EphemeralDisk: &EphemeralDisk{
  2012  					Migrate: false,
  2013  					Sticky:  false,
  2014  					SizeMB:  100,
  2015  				},
  2016  			},
  2017  			New: &TaskGroup{
  2018  				EphemeralDisk: &EphemeralDisk{
  2019  					Migrate: true,
  2020  					Sticky:  true,
  2021  					SizeMB:  90,
  2022  				},
  2023  			},
  2024  			Expected: &TaskGroupDiff{
  2025  				Type: DiffTypeEdited,
  2026  				Objects: []*ObjectDiff{
  2027  					{
  2028  						Type: DiffTypeEdited,
  2029  						Name: "EphemeralDisk",
  2030  						Fields: []*FieldDiff{
  2031  							{
  2032  								Type: DiffTypeEdited,
  2033  								Name: "Migrate",
  2034  								Old:  "false",
  2035  								New:  "true",
  2036  							},
  2037  							{
  2038  								Type: DiffTypeEdited,
  2039  								Name: "SizeMB",
  2040  								Old:  "100",
  2041  								New:  "90",
  2042  							},
  2043  							{
  2044  								Type: DiffTypeEdited,
  2045  								Name: "Sticky",
  2046  								Old:  "false",
  2047  								New:  "true",
  2048  							},
  2049  						},
  2050  					},
  2051  				},
  2052  			},
  2053  		},
  2054  		{
  2055  			// Tasks edited
  2056  			Old: &TaskGroup{
  2057  				Tasks: []*Task{
  2058  					{
  2059  						Name:   "foo",
  2060  						Driver: "docker",
  2061  					},
  2062  					{
  2063  						Name:   "bar",
  2064  						Driver: "docker",
  2065  					},
  2066  					{
  2067  						Name:          "baz",
  2068  						ShutdownDelay: 1 * time.Second,
  2069  					},
  2070  				},
  2071  			},
  2072  			New: &TaskGroup{
  2073  				Tasks: []*Task{
  2074  					{
  2075  						Name:   "bar",
  2076  						Driver: "docker",
  2077  					},
  2078  					{
  2079  						Name:   "bam",
  2080  						Driver: "docker",
  2081  					},
  2082  					{
  2083  						Name:          "baz",
  2084  						ShutdownDelay: 2 * time.Second,
  2085  					},
  2086  				},
  2087  			},
  2088  			Expected: &TaskGroupDiff{
  2089  				Type: DiffTypeEdited,
  2090  				Tasks: []*TaskDiff{
  2091  					{
  2092  						Type: DiffTypeAdded,
  2093  						Name: "bam",
  2094  						Fields: []*FieldDiff{
  2095  							{
  2096  								Type: DiffTypeAdded,
  2097  								Name: "Driver",
  2098  								Old:  "",
  2099  								New:  "docker",
  2100  							},
  2101  							{
  2102  								Type: DiffTypeAdded,
  2103  								Name: "KillTimeout",
  2104  								Old:  "",
  2105  								New:  "0",
  2106  							},
  2107  							{
  2108  								Type: DiffTypeAdded,
  2109  								Name: "Leader",
  2110  								Old:  "",
  2111  								New:  "false",
  2112  							},
  2113  							{
  2114  								Type: DiffTypeAdded,
  2115  								Name: "ShutdownDelay",
  2116  								Old:  "",
  2117  								New:  "0",
  2118  							},
  2119  						},
  2120  					},
  2121  					{
  2122  						Type: DiffTypeNone,
  2123  						Name: "bar",
  2124  					},
  2125  					{
  2126  						Type: DiffTypeEdited,
  2127  						Name: "baz",
  2128  						Fields: []*FieldDiff{
  2129  							{
  2130  								Type: DiffTypeEdited,
  2131  								Name: "ShutdownDelay",
  2132  								Old:  "1000000000",
  2133  								New:  "2000000000",
  2134  							},
  2135  						},
  2136  					},
  2137  					{
  2138  						Type: DiffTypeDeleted,
  2139  						Name: "foo",
  2140  						Fields: []*FieldDiff{
  2141  							{
  2142  								Type: DiffTypeDeleted,
  2143  								Name: "Driver",
  2144  								Old:  "docker",
  2145  								New:  "",
  2146  							},
  2147  							{
  2148  								Type: DiffTypeDeleted,
  2149  								Name: "KillTimeout",
  2150  								Old:  "0",
  2151  								New:  "",
  2152  							},
  2153  							{
  2154  								Type: DiffTypeDeleted,
  2155  								Name: "Leader",
  2156  								Old:  "false",
  2157  								New:  "",
  2158  							},
  2159  							{
  2160  								Type: DiffTypeDeleted,
  2161  								Name: "ShutdownDelay",
  2162  								Old:  "0",
  2163  								New:  "",
  2164  							},
  2165  						},
  2166  					},
  2167  				},
  2168  			},
  2169  		},
  2170  	}
  2171  
  2172  	for i, c := range cases {
  2173  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2174  		if c.Error && err == nil {
  2175  			t.Fatalf("case %d: expected errored", i+1)
  2176  		} else if err != nil {
  2177  			if !c.Error {
  2178  				t.Fatalf("case %d: errored %#v", i+1, err)
  2179  			} else {
  2180  				continue
  2181  			}
  2182  		}
  2183  
  2184  		if !reflect.DeepEqual(actual, c.Expected) {
  2185  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2186  				i+1, actual, c.Expected)
  2187  		}
  2188  	}
  2189  }
  2190  
  2191  func TestTaskDiff(t *testing.T) {
  2192  	cases := []struct {
  2193  		Name       string
  2194  		Old, New   *Task
  2195  		Expected   *TaskDiff
  2196  		Error      bool
  2197  		Contextual bool
  2198  	}{
  2199  		{
  2200  			Name: "Empty",
  2201  			Old:  nil,
  2202  			New:  nil,
  2203  			Expected: &TaskDiff{
  2204  				Type: DiffTypeNone,
  2205  			},
  2206  		},
  2207  		{
  2208  			Name: "Primitive only that has different names",
  2209  			Old: &Task{
  2210  				Name: "foo",
  2211  				Meta: map[string]string{
  2212  					"foo": "bar",
  2213  				},
  2214  			},
  2215  			New: &Task{
  2216  				Name: "bar",
  2217  				Meta: map[string]string{
  2218  					"foo": "bar",
  2219  				},
  2220  			},
  2221  			Error: true,
  2222  		},
  2223  		{
  2224  			Name: "Primitive only that is the same",
  2225  			Old: &Task{
  2226  				Name:   "foo",
  2227  				Driver: "exec",
  2228  				User:   "foo",
  2229  				Env: map[string]string{
  2230  					"FOO": "bar",
  2231  				},
  2232  				Meta: map[string]string{
  2233  					"foo": "bar",
  2234  				},
  2235  				KillTimeout: 1 * time.Second,
  2236  				Leader:      true,
  2237  			},
  2238  			New: &Task{
  2239  				Name:   "foo",
  2240  				Driver: "exec",
  2241  				User:   "foo",
  2242  				Env: map[string]string{
  2243  					"FOO": "bar",
  2244  				},
  2245  				Meta: map[string]string{
  2246  					"foo": "bar",
  2247  				},
  2248  				KillTimeout: 1 * time.Second,
  2249  				Leader:      true,
  2250  			},
  2251  			Expected: &TaskDiff{
  2252  				Type: DiffTypeNone,
  2253  				Name: "foo",
  2254  			},
  2255  		},
  2256  		{
  2257  			Name: "Primitive only that has diffs",
  2258  			Old: &Task{
  2259  				Name:   "foo",
  2260  				Driver: "exec",
  2261  				User:   "foo",
  2262  				Env: map[string]string{
  2263  					"FOO": "bar",
  2264  				},
  2265  				Meta: map[string]string{
  2266  					"foo": "bar",
  2267  				},
  2268  				KillTimeout: 1 * time.Second,
  2269  				Leader:      true,
  2270  			},
  2271  			New: &Task{
  2272  				Name:   "foo",
  2273  				Driver: "docker",
  2274  				User:   "bar",
  2275  				Env: map[string]string{
  2276  					"FOO": "baz",
  2277  				},
  2278  				Meta: map[string]string{
  2279  					"foo": "baz",
  2280  				},
  2281  				KillTimeout: 2 * time.Second,
  2282  				Leader:      false,
  2283  			},
  2284  			Expected: &TaskDiff{
  2285  				Type: DiffTypeEdited,
  2286  				Name: "foo",
  2287  				Fields: []*FieldDiff{
  2288  					{
  2289  						Type: DiffTypeEdited,
  2290  						Name: "Driver",
  2291  						Old:  "exec",
  2292  						New:  "docker",
  2293  					},
  2294  					{
  2295  						Type: DiffTypeEdited,
  2296  						Name: "Env[FOO]",
  2297  						Old:  "bar",
  2298  						New:  "baz",
  2299  					},
  2300  					{
  2301  						Type: DiffTypeEdited,
  2302  						Name: "KillTimeout",
  2303  						Old:  "1000000000",
  2304  						New:  "2000000000",
  2305  					},
  2306  					{
  2307  						Type: DiffTypeEdited,
  2308  						Name: "Leader",
  2309  						Old:  "true",
  2310  						New:  "false",
  2311  					},
  2312  					{
  2313  						Type: DiffTypeEdited,
  2314  						Name: "Meta[foo]",
  2315  						Old:  "bar",
  2316  						New:  "baz",
  2317  					},
  2318  					{
  2319  						Type: DiffTypeEdited,
  2320  						Name: "User",
  2321  						Old:  "foo",
  2322  						New:  "bar",
  2323  					},
  2324  				},
  2325  			},
  2326  		},
  2327  		{
  2328  			Name: "Map diff",
  2329  			Old: &Task{
  2330  				Meta: map[string]string{
  2331  					"foo": "foo",
  2332  					"bar": "bar",
  2333  				},
  2334  				Env: map[string]string{
  2335  					"foo": "foo",
  2336  					"bar": "bar",
  2337  				},
  2338  			},
  2339  			New: &Task{
  2340  				Meta: map[string]string{
  2341  					"bar": "bar",
  2342  					"baz": "baz",
  2343  				},
  2344  				Env: map[string]string{
  2345  					"bar": "bar",
  2346  					"baz": "baz",
  2347  				},
  2348  			},
  2349  			Expected: &TaskDiff{
  2350  				Type: DiffTypeEdited,
  2351  				Fields: []*FieldDiff{
  2352  					{
  2353  						Type: DiffTypeAdded,
  2354  						Name: "Env[baz]",
  2355  						Old:  "",
  2356  						New:  "baz",
  2357  					},
  2358  					{
  2359  						Type: DiffTypeDeleted,
  2360  						Name: "Env[foo]",
  2361  						Old:  "foo",
  2362  						New:  "",
  2363  					},
  2364  					{
  2365  						Type: DiffTypeAdded,
  2366  						Name: "Meta[baz]",
  2367  						Old:  "",
  2368  						New:  "baz",
  2369  					},
  2370  					{
  2371  						Type: DiffTypeDeleted,
  2372  						Name: "Meta[foo]",
  2373  						Old:  "foo",
  2374  						New:  "",
  2375  					},
  2376  				},
  2377  			},
  2378  		},
  2379  		{
  2380  			Name: "Constraints edited",
  2381  			Old: &Task{
  2382  				Constraints: []*Constraint{
  2383  					{
  2384  						LTarget: "foo",
  2385  						RTarget: "foo",
  2386  						Operand: "foo",
  2387  						str:     "foo",
  2388  					},
  2389  					{
  2390  						LTarget: "bar",
  2391  						RTarget: "bar",
  2392  						Operand: "bar",
  2393  						str:     "bar",
  2394  					},
  2395  				},
  2396  			},
  2397  			New: &Task{
  2398  				Constraints: []*Constraint{
  2399  					{
  2400  						LTarget: "foo",
  2401  						RTarget: "foo",
  2402  						Operand: "foo",
  2403  						str:     "foo",
  2404  					},
  2405  					{
  2406  						LTarget: "baz",
  2407  						RTarget: "baz",
  2408  						Operand: "baz",
  2409  						str:     "baz",
  2410  					},
  2411  				},
  2412  			},
  2413  			Expected: &TaskDiff{
  2414  				Type: DiffTypeEdited,
  2415  				Objects: []*ObjectDiff{
  2416  					{
  2417  						Type: DiffTypeAdded,
  2418  						Name: "Constraint",
  2419  						Fields: []*FieldDiff{
  2420  							{
  2421  								Type: DiffTypeAdded,
  2422  								Name: "LTarget",
  2423  								Old:  "",
  2424  								New:  "baz",
  2425  							},
  2426  							{
  2427  								Type: DiffTypeAdded,
  2428  								Name: "Operand",
  2429  								Old:  "",
  2430  								New:  "baz",
  2431  							},
  2432  							{
  2433  								Type: DiffTypeAdded,
  2434  								Name: "RTarget",
  2435  								Old:  "",
  2436  								New:  "baz",
  2437  							},
  2438  						},
  2439  					},
  2440  					{
  2441  						Type: DiffTypeDeleted,
  2442  						Name: "Constraint",
  2443  						Fields: []*FieldDiff{
  2444  							{
  2445  								Type: DiffTypeDeleted,
  2446  								Name: "LTarget",
  2447  								Old:  "bar",
  2448  								New:  "",
  2449  							},
  2450  							{
  2451  								Type: DiffTypeDeleted,
  2452  								Name: "Operand",
  2453  								Old:  "bar",
  2454  								New:  "",
  2455  							},
  2456  							{
  2457  								Type: DiffTypeDeleted,
  2458  								Name: "RTarget",
  2459  								Old:  "bar",
  2460  								New:  "",
  2461  							},
  2462  						},
  2463  					},
  2464  				},
  2465  			},
  2466  		},
  2467  		{
  2468  			Name: "LogConfig added",
  2469  			Old:  &Task{},
  2470  			New: &Task{
  2471  				LogConfig: &LogConfig{
  2472  					MaxFiles:      1,
  2473  					MaxFileSizeMB: 10,
  2474  				},
  2475  			},
  2476  			Expected: &TaskDiff{
  2477  				Type: DiffTypeEdited,
  2478  				Objects: []*ObjectDiff{
  2479  					{
  2480  						Type: DiffTypeAdded,
  2481  						Name: "LogConfig",
  2482  						Fields: []*FieldDiff{
  2483  							{
  2484  								Type: DiffTypeAdded,
  2485  								Name: "MaxFileSizeMB",
  2486  								Old:  "",
  2487  								New:  "10",
  2488  							},
  2489  							{
  2490  								Type: DiffTypeAdded,
  2491  								Name: "MaxFiles",
  2492  								Old:  "",
  2493  								New:  "1",
  2494  							},
  2495  						},
  2496  					},
  2497  				},
  2498  			},
  2499  		},
  2500  		{
  2501  			Name: "LogConfig deleted",
  2502  			Old: &Task{
  2503  				LogConfig: &LogConfig{
  2504  					MaxFiles:      1,
  2505  					MaxFileSizeMB: 10,
  2506  				},
  2507  			},
  2508  			New: &Task{},
  2509  			Expected: &TaskDiff{
  2510  				Type: DiffTypeEdited,
  2511  				Objects: []*ObjectDiff{
  2512  					{
  2513  						Type: DiffTypeDeleted,
  2514  						Name: "LogConfig",
  2515  						Fields: []*FieldDiff{
  2516  							{
  2517  								Type: DiffTypeDeleted,
  2518  								Name: "MaxFileSizeMB",
  2519  								Old:  "10",
  2520  								New:  "",
  2521  							},
  2522  							{
  2523  								Type: DiffTypeDeleted,
  2524  								Name: "MaxFiles",
  2525  								Old:  "1",
  2526  								New:  "",
  2527  							},
  2528  						},
  2529  					},
  2530  				},
  2531  			},
  2532  		},
  2533  		{
  2534  			Name: "LogConfig edited",
  2535  			Old: &Task{
  2536  				LogConfig: &LogConfig{
  2537  					MaxFiles:      1,
  2538  					MaxFileSizeMB: 10,
  2539  				},
  2540  			},
  2541  			New: &Task{
  2542  				LogConfig: &LogConfig{
  2543  					MaxFiles:      2,
  2544  					MaxFileSizeMB: 20,
  2545  				},
  2546  			},
  2547  			Expected: &TaskDiff{
  2548  				Type: DiffTypeEdited,
  2549  				Objects: []*ObjectDiff{
  2550  					{
  2551  						Type: DiffTypeEdited,
  2552  						Name: "LogConfig",
  2553  						Fields: []*FieldDiff{
  2554  							{
  2555  								Type: DiffTypeEdited,
  2556  								Name: "MaxFileSizeMB",
  2557  								Old:  "10",
  2558  								New:  "20",
  2559  							},
  2560  							{
  2561  								Type: DiffTypeEdited,
  2562  								Name: "MaxFiles",
  2563  								Old:  "1",
  2564  								New:  "2",
  2565  							},
  2566  						},
  2567  					},
  2568  				},
  2569  			},
  2570  		},
  2571  		{
  2572  			Name:       "LogConfig edited with context",
  2573  			Contextual: true,
  2574  			Old: &Task{
  2575  				LogConfig: &LogConfig{
  2576  					MaxFiles:      1,
  2577  					MaxFileSizeMB: 10,
  2578  				},
  2579  			},
  2580  			New: &Task{
  2581  				LogConfig: &LogConfig{
  2582  					MaxFiles:      1,
  2583  					MaxFileSizeMB: 20,
  2584  				},
  2585  			},
  2586  			Expected: &TaskDiff{
  2587  				Type: DiffTypeEdited,
  2588  				Objects: []*ObjectDiff{
  2589  					{
  2590  						Type: DiffTypeEdited,
  2591  						Name: "LogConfig",
  2592  						Fields: []*FieldDiff{
  2593  							{
  2594  								Type: DiffTypeEdited,
  2595  								Name: "MaxFileSizeMB",
  2596  								Old:  "10",
  2597  								New:  "20",
  2598  							},
  2599  							{
  2600  								Type: DiffTypeNone,
  2601  								Name: "MaxFiles",
  2602  								Old:  "1",
  2603  								New:  "1",
  2604  							},
  2605  						},
  2606  					},
  2607  				},
  2608  			},
  2609  		},
  2610  		{
  2611  			Name: "Artifacts edited",
  2612  			Old: &Task{
  2613  				Artifacts: []*TaskArtifact{
  2614  					{
  2615  						GetterSource: "foo",
  2616  						GetterOptions: map[string]string{
  2617  							"foo": "bar",
  2618  						},
  2619  						RelativeDest: "foo",
  2620  					},
  2621  					{
  2622  						GetterSource: "bar",
  2623  						GetterOptions: map[string]string{
  2624  							"bar": "baz",
  2625  						},
  2626  						GetterMode:   "dir",
  2627  						RelativeDest: "bar",
  2628  					},
  2629  				},
  2630  			},
  2631  			New: &Task{
  2632  				Artifacts: []*TaskArtifact{
  2633  					{
  2634  						GetterSource: "foo",
  2635  						GetterOptions: map[string]string{
  2636  							"foo": "bar",
  2637  						},
  2638  						RelativeDest: "foo",
  2639  					},
  2640  					{
  2641  						GetterSource: "bam",
  2642  						GetterOptions: map[string]string{
  2643  							"bam": "baz",
  2644  						},
  2645  						GetterMode:   "file",
  2646  						RelativeDest: "bam",
  2647  					},
  2648  				},
  2649  			},
  2650  			Expected: &TaskDiff{
  2651  				Type: DiffTypeEdited,
  2652  				Objects: []*ObjectDiff{
  2653  					{
  2654  						Type: DiffTypeAdded,
  2655  						Name: "Artifact",
  2656  						Fields: []*FieldDiff{
  2657  							{
  2658  								Type: DiffTypeAdded,
  2659  								Name: "GetterMode",
  2660  								Old:  "",
  2661  								New:  "file",
  2662  							},
  2663  							{
  2664  								Type: DiffTypeAdded,
  2665  								Name: "GetterOptions[bam]",
  2666  								Old:  "",
  2667  								New:  "baz",
  2668  							},
  2669  							{
  2670  								Type: DiffTypeAdded,
  2671  								Name: "GetterSource",
  2672  								Old:  "",
  2673  								New:  "bam",
  2674  							},
  2675  							{
  2676  								Type: DiffTypeAdded,
  2677  								Name: "RelativeDest",
  2678  								Old:  "",
  2679  								New:  "bam",
  2680  							},
  2681  						},
  2682  					},
  2683  					{
  2684  						Type: DiffTypeDeleted,
  2685  						Name: "Artifact",
  2686  						Fields: []*FieldDiff{
  2687  							{
  2688  								Type: DiffTypeDeleted,
  2689  								Name: "GetterMode",
  2690  								Old:  "dir",
  2691  								New:  "",
  2692  							},
  2693  							{
  2694  								Type: DiffTypeDeleted,
  2695  								Name: "GetterOptions[bar]",
  2696  								Old:  "baz",
  2697  								New:  "",
  2698  							},
  2699  							{
  2700  								Type: DiffTypeDeleted,
  2701  								Name: "GetterSource",
  2702  								Old:  "bar",
  2703  								New:  "",
  2704  							},
  2705  							{
  2706  								Type: DiffTypeDeleted,
  2707  								Name: "RelativeDest",
  2708  								Old:  "bar",
  2709  								New:  "",
  2710  							},
  2711  						},
  2712  					},
  2713  				},
  2714  			},
  2715  		},
  2716  		{
  2717  			Name: "Resources edited (no networks)",
  2718  			Old: &Task{
  2719  				Resources: &Resources{
  2720  					CPU:      100,
  2721  					MemoryMB: 100,
  2722  					DiskMB:   100,
  2723  					IOPS:     100,
  2724  				},
  2725  			},
  2726  			New: &Task{
  2727  				Resources: &Resources{
  2728  					CPU:      200,
  2729  					MemoryMB: 200,
  2730  					DiskMB:   200,
  2731  					IOPS:     200,
  2732  				},
  2733  			},
  2734  			Expected: &TaskDiff{
  2735  				Type: DiffTypeEdited,
  2736  				Objects: []*ObjectDiff{
  2737  					{
  2738  						Type: DiffTypeEdited,
  2739  						Name: "Resources",
  2740  						Fields: []*FieldDiff{
  2741  							{
  2742  								Type: DiffTypeEdited,
  2743  								Name: "CPU",
  2744  								Old:  "100",
  2745  								New:  "200",
  2746  							},
  2747  							{
  2748  								Type: DiffTypeEdited,
  2749  								Name: "DiskMB",
  2750  								Old:  "100",
  2751  								New:  "200",
  2752  							},
  2753  							{
  2754  								Type: DiffTypeEdited,
  2755  								Name: "IOPS",
  2756  								Old:  "100",
  2757  								New:  "200",
  2758  							},
  2759  							{
  2760  								Type: DiffTypeEdited,
  2761  								Name: "MemoryMB",
  2762  								Old:  "100",
  2763  								New:  "200",
  2764  							},
  2765  						},
  2766  					},
  2767  				},
  2768  			},
  2769  		},
  2770  		{
  2771  			Name:       "Resources edited (no networks) with context",
  2772  			Contextual: true,
  2773  			Old: &Task{
  2774  				Resources: &Resources{
  2775  					CPU:      100,
  2776  					MemoryMB: 100,
  2777  					DiskMB:   100,
  2778  					IOPS:     100,
  2779  				},
  2780  			},
  2781  			New: &Task{
  2782  				Resources: &Resources{
  2783  					CPU:      200,
  2784  					MemoryMB: 100,
  2785  					DiskMB:   200,
  2786  					IOPS:     100,
  2787  				},
  2788  			},
  2789  			Expected: &TaskDiff{
  2790  				Type: DiffTypeEdited,
  2791  				Objects: []*ObjectDiff{
  2792  					{
  2793  						Type: DiffTypeEdited,
  2794  						Name: "Resources",
  2795  						Fields: []*FieldDiff{
  2796  							{
  2797  								Type: DiffTypeEdited,
  2798  								Name: "CPU",
  2799  								Old:  "100",
  2800  								New:  "200",
  2801  							},
  2802  							{
  2803  								Type: DiffTypeEdited,
  2804  								Name: "DiskMB",
  2805  								Old:  "100",
  2806  								New:  "200",
  2807  							},
  2808  							{
  2809  								Type: DiffTypeNone,
  2810  								Name: "IOPS",
  2811  								Old:  "100",
  2812  								New:  "100",
  2813  							},
  2814  							{
  2815  								Type: DiffTypeNone,
  2816  								Name: "MemoryMB",
  2817  								Old:  "100",
  2818  								New:  "100",
  2819  							},
  2820  						},
  2821  					},
  2822  				},
  2823  			},
  2824  		},
  2825  		{
  2826  			Name: "Network Resources edited",
  2827  			Old: &Task{
  2828  				Resources: &Resources{
  2829  					Networks: []*NetworkResource{
  2830  						{
  2831  							Device: "foo",
  2832  							CIDR:   "foo",
  2833  							IP:     "foo",
  2834  							MBits:  100,
  2835  							ReservedPorts: []Port{
  2836  								{
  2837  									Label: "foo",
  2838  									Value: 80,
  2839  								},
  2840  							},
  2841  							DynamicPorts: []Port{
  2842  								{
  2843  									Label: "bar",
  2844  								},
  2845  							},
  2846  						},
  2847  					},
  2848  				},
  2849  			},
  2850  			New: &Task{
  2851  				Resources: &Resources{
  2852  					Networks: []*NetworkResource{
  2853  						{
  2854  							Device: "bar",
  2855  							CIDR:   "bar",
  2856  							IP:     "bar",
  2857  							MBits:  200,
  2858  							ReservedPorts: []Port{
  2859  								{
  2860  									Label: "foo",
  2861  									Value: 81,
  2862  								},
  2863  							},
  2864  							DynamicPorts: []Port{
  2865  								{
  2866  									Label: "baz",
  2867  								},
  2868  							},
  2869  						},
  2870  					},
  2871  				},
  2872  			},
  2873  			Expected: &TaskDiff{
  2874  				Type: DiffTypeEdited,
  2875  				Objects: []*ObjectDiff{
  2876  					{
  2877  						Type: DiffTypeEdited,
  2878  						Name: "Resources",
  2879  						Objects: []*ObjectDiff{
  2880  							{
  2881  								Type: DiffTypeAdded,
  2882  								Name: "Network",
  2883  								Fields: []*FieldDiff{
  2884  									{
  2885  										Type: DiffTypeAdded,
  2886  										Name: "MBits",
  2887  										Old:  "",
  2888  										New:  "200",
  2889  									},
  2890  								},
  2891  								Objects: []*ObjectDiff{
  2892  									{
  2893  										Type: DiffTypeAdded,
  2894  										Name: "Static Port",
  2895  										Fields: []*FieldDiff{
  2896  											{
  2897  												Type: DiffTypeAdded,
  2898  												Name: "Label",
  2899  												Old:  "",
  2900  												New:  "foo",
  2901  											},
  2902  											{
  2903  												Type: DiffTypeAdded,
  2904  												Name: "Value",
  2905  												Old:  "",
  2906  												New:  "81",
  2907  											},
  2908  										},
  2909  									},
  2910  									{
  2911  										Type: DiffTypeAdded,
  2912  										Name: "Dynamic Port",
  2913  										Fields: []*FieldDiff{
  2914  											{
  2915  												Type: DiffTypeAdded,
  2916  												Name: "Label",
  2917  												Old:  "",
  2918  												New:  "baz",
  2919  											},
  2920  										},
  2921  									},
  2922  								},
  2923  							},
  2924  							{
  2925  								Type: DiffTypeDeleted,
  2926  								Name: "Network",
  2927  								Fields: []*FieldDiff{
  2928  									{
  2929  										Type: DiffTypeDeleted,
  2930  										Name: "MBits",
  2931  										Old:  "100",
  2932  										New:  "",
  2933  									},
  2934  								},
  2935  								Objects: []*ObjectDiff{
  2936  									{
  2937  										Type: DiffTypeDeleted,
  2938  										Name: "Static Port",
  2939  										Fields: []*FieldDiff{
  2940  											{
  2941  												Type: DiffTypeDeleted,
  2942  												Name: "Label",
  2943  												Old:  "foo",
  2944  												New:  "",
  2945  											},
  2946  											{
  2947  												Type: DiffTypeDeleted,
  2948  												Name: "Value",
  2949  												Old:  "80",
  2950  												New:  "",
  2951  											},
  2952  										},
  2953  									},
  2954  									{
  2955  										Type: DiffTypeDeleted,
  2956  										Name: "Dynamic Port",
  2957  										Fields: []*FieldDiff{
  2958  											{
  2959  												Type: DiffTypeDeleted,
  2960  												Name: "Label",
  2961  												Old:  "bar",
  2962  												New:  "",
  2963  											},
  2964  										},
  2965  									},
  2966  								},
  2967  							},
  2968  						},
  2969  					},
  2970  				},
  2971  			},
  2972  		},
  2973  		{
  2974  			Name: "Config same",
  2975  			Old: &Task{
  2976  				Config: map[string]interface{}{
  2977  					"foo": 1,
  2978  					"bar": "bar",
  2979  					"bam": []string{"a", "b"},
  2980  					"baz": map[string]int{
  2981  						"a": 1,
  2982  						"b": 2,
  2983  					},
  2984  					"boom": &Port{
  2985  						Label: "boom_port",
  2986  					},
  2987  				},
  2988  			},
  2989  			New: &Task{
  2990  				Config: map[string]interface{}{
  2991  					"foo": 1,
  2992  					"bar": "bar",
  2993  					"bam": []string{"a", "b"},
  2994  					"baz": map[string]int{
  2995  						"a": 1,
  2996  						"b": 2,
  2997  					},
  2998  					"boom": &Port{
  2999  						Label: "boom_port",
  3000  					},
  3001  				},
  3002  			},
  3003  			Expected: &TaskDiff{
  3004  				Type: DiffTypeNone,
  3005  			},
  3006  		},
  3007  		{
  3008  			Name: "Config edited",
  3009  			Old: &Task{
  3010  				Config: map[string]interface{}{
  3011  					"foo": 1,
  3012  					"bar": "baz",
  3013  					"bam": []string{"a", "b"},
  3014  					"baz": map[string]int{
  3015  						"a": 1,
  3016  						"b": 2,
  3017  					},
  3018  					"boom": &Port{
  3019  						Label: "boom_port",
  3020  					},
  3021  				},
  3022  			},
  3023  			New: &Task{
  3024  				Config: map[string]interface{}{
  3025  					"foo": 2,
  3026  					"bar": "baz",
  3027  					"bam": []string{"a", "c", "d"},
  3028  					"baz": map[string]int{
  3029  						"b": 3,
  3030  						"c": 4,
  3031  					},
  3032  					"boom": &Port{
  3033  						Label: "boom_port2",
  3034  					},
  3035  				},
  3036  			},
  3037  			Expected: &TaskDiff{
  3038  				Type: DiffTypeEdited,
  3039  				Objects: []*ObjectDiff{
  3040  					{
  3041  						Type: DiffTypeEdited,
  3042  						Name: "Config",
  3043  						Fields: []*FieldDiff{
  3044  							{
  3045  								Type: DiffTypeEdited,
  3046  								Name: "bam[1]",
  3047  								Old:  "b",
  3048  								New:  "c",
  3049  							},
  3050  							{
  3051  								Type: DiffTypeAdded,
  3052  								Name: "bam[2]",
  3053  								Old:  "",
  3054  								New:  "d",
  3055  							},
  3056  							{
  3057  								Type: DiffTypeDeleted,
  3058  								Name: "baz[a]",
  3059  								Old:  "1",
  3060  								New:  "",
  3061  							},
  3062  							{
  3063  								Type: DiffTypeEdited,
  3064  								Name: "baz[b]",
  3065  								Old:  "2",
  3066  								New:  "3",
  3067  							},
  3068  							{
  3069  								Type: DiffTypeAdded,
  3070  								Name: "baz[c]",
  3071  								Old:  "",
  3072  								New:  "4",
  3073  							},
  3074  							{
  3075  								Type: DiffTypeEdited,
  3076  								Name: "boom.Label",
  3077  								Old:  "boom_port",
  3078  								New:  "boom_port2",
  3079  							},
  3080  							{
  3081  								Type: DiffTypeEdited,
  3082  								Name: "foo",
  3083  								Old:  "1",
  3084  								New:  "2",
  3085  							},
  3086  						},
  3087  					},
  3088  				},
  3089  			},
  3090  		},
  3091  		{
  3092  			Name:       "Config edited with context",
  3093  			Contextual: true,
  3094  			Old: &Task{
  3095  				Config: map[string]interface{}{
  3096  					"foo": 1,
  3097  					"bar": "baz",
  3098  					"bam": []string{"a", "b"},
  3099  					"baz": map[string]int{
  3100  						"a": 1,
  3101  						"b": 2,
  3102  					},
  3103  					"boom": &Port{
  3104  						Label: "boom_port",
  3105  					},
  3106  				},
  3107  			},
  3108  			New: &Task{
  3109  				Config: map[string]interface{}{
  3110  					"foo": 2,
  3111  					"bar": "baz",
  3112  					"bam": []string{"a", "c", "d"},
  3113  					"baz": map[string]int{
  3114  						"a": 1,
  3115  						"b": 2,
  3116  					},
  3117  					"boom": &Port{
  3118  						Label: "boom_port",
  3119  					},
  3120  				},
  3121  			},
  3122  			Expected: &TaskDiff{
  3123  				Type: DiffTypeEdited,
  3124  				Objects: []*ObjectDiff{
  3125  					{
  3126  						Type: DiffTypeEdited,
  3127  						Name: "Config",
  3128  						Fields: []*FieldDiff{
  3129  							{
  3130  								Type: DiffTypeNone,
  3131  								Name: "bam[0]",
  3132  								Old:  "a",
  3133  								New:  "a",
  3134  							},
  3135  							{
  3136  								Type: DiffTypeEdited,
  3137  								Name: "bam[1]",
  3138  								Old:  "b",
  3139  								New:  "c",
  3140  							},
  3141  							{
  3142  								Type: DiffTypeAdded,
  3143  								Name: "bam[2]",
  3144  								Old:  "",
  3145  								New:  "d",
  3146  							},
  3147  							{
  3148  								Type: DiffTypeNone,
  3149  								Name: "bar",
  3150  								Old:  "baz",
  3151  								New:  "baz",
  3152  							},
  3153  							{
  3154  								Type: DiffTypeNone,
  3155  								Name: "baz[a]",
  3156  								Old:  "1",
  3157  								New:  "1",
  3158  							},
  3159  							{
  3160  								Type: DiffTypeNone,
  3161  								Name: "baz[b]",
  3162  								Old:  "2",
  3163  								New:  "2",
  3164  							},
  3165  							{
  3166  								Type: DiffTypeNone,
  3167  								Name: "boom.Label",
  3168  								Old:  "boom_port",
  3169  								New:  "boom_port",
  3170  							},
  3171  							{
  3172  								Type: DiffTypeNone,
  3173  								Name: "boom.Value",
  3174  								Old:  "0",
  3175  								New:  "0",
  3176  							},
  3177  							{
  3178  								Type: DiffTypeEdited,
  3179  								Name: "foo",
  3180  								Old:  "1",
  3181  								New:  "2",
  3182  							},
  3183  						},
  3184  					},
  3185  				},
  3186  			},
  3187  		},
  3188  		{
  3189  			Name: "Services edited (no checks)",
  3190  			Old: &Task{
  3191  				Services: []*Service{
  3192  					{
  3193  						Name:      "foo",
  3194  						PortLabel: "foo",
  3195  					},
  3196  					{
  3197  						Name:      "bar",
  3198  						PortLabel: "bar",
  3199  					},
  3200  					{
  3201  						Name:      "baz",
  3202  						PortLabel: "baz",
  3203  					},
  3204  				},
  3205  			},
  3206  			New: &Task{
  3207  				Services: []*Service{
  3208  					{
  3209  						Name:      "bar",
  3210  						PortLabel: "bar",
  3211  					},
  3212  					{
  3213  						Name:      "baz",
  3214  						PortLabel: "baz2",
  3215  					},
  3216  					{
  3217  						Name:      "bam",
  3218  						PortLabel: "bam",
  3219  					},
  3220  				},
  3221  			},
  3222  			Expected: &TaskDiff{
  3223  				Type: DiffTypeEdited,
  3224  				Objects: []*ObjectDiff{
  3225  					{
  3226  						Type: DiffTypeEdited,
  3227  						Name: "Service",
  3228  						Fields: []*FieldDiff{
  3229  							{
  3230  								Type: DiffTypeEdited,
  3231  								Name: "PortLabel",
  3232  								Old:  "baz",
  3233  								New:  "baz2",
  3234  							},
  3235  						},
  3236  					},
  3237  					{
  3238  						Type: DiffTypeAdded,
  3239  						Name: "Service",
  3240  						Fields: []*FieldDiff{
  3241  							{
  3242  								Type: DiffTypeAdded,
  3243  								Name: "Name",
  3244  								Old:  "",
  3245  								New:  "bam",
  3246  							},
  3247  							{
  3248  								Type: DiffTypeAdded,
  3249  								Name: "PortLabel",
  3250  								Old:  "",
  3251  								New:  "bam",
  3252  							},
  3253  						},
  3254  					},
  3255  					{
  3256  						Type: DiffTypeDeleted,
  3257  						Name: "Service",
  3258  						Fields: []*FieldDiff{
  3259  							{
  3260  								Type: DiffTypeDeleted,
  3261  								Name: "Name",
  3262  								Old:  "foo",
  3263  								New:  "",
  3264  							},
  3265  							{
  3266  								Type: DiffTypeDeleted,
  3267  								Name: "PortLabel",
  3268  								Old:  "foo",
  3269  								New:  "",
  3270  							},
  3271  						},
  3272  					},
  3273  				},
  3274  			},
  3275  		},
  3276  		{
  3277  			Name:       "Services edited (no checks) with context",
  3278  			Contextual: true,
  3279  			Old: &Task{
  3280  				Services: []*Service{
  3281  					{
  3282  						Name:      "foo",
  3283  						PortLabel: "foo",
  3284  					},
  3285  				},
  3286  			},
  3287  			New: &Task{
  3288  				Services: []*Service{
  3289  					{
  3290  						Name:        "foo",
  3291  						PortLabel:   "bar",
  3292  						AddressMode: "driver",
  3293  					},
  3294  				},
  3295  			},
  3296  			Expected: &TaskDiff{
  3297  				Type: DiffTypeEdited,
  3298  				Objects: []*ObjectDiff{
  3299  					{
  3300  						Type: DiffTypeEdited,
  3301  						Name: "Service",
  3302  						Fields: []*FieldDiff{
  3303  							{
  3304  								Type: DiffTypeAdded,
  3305  								Name: "AddressMode",
  3306  								New:  "driver",
  3307  							},
  3308  							{
  3309  								Type: DiffTypeNone,
  3310  								Name: "Name",
  3311  								Old:  "foo",
  3312  								New:  "foo",
  3313  							},
  3314  							{
  3315  								Type: DiffTypeEdited,
  3316  								Name: "PortLabel",
  3317  								Old:  "foo",
  3318  								New:  "bar",
  3319  							},
  3320  						},
  3321  					},
  3322  				},
  3323  			},
  3324  		},
  3325  		{
  3326  			Name: "Service Checks edited",
  3327  			Old: &Task{
  3328  				Services: []*Service{
  3329  					{
  3330  						Name: "foo",
  3331  						Checks: []*ServiceCheck{
  3332  							{
  3333  								Name:     "foo",
  3334  								Type:     "http",
  3335  								Command:  "foo",
  3336  								Args:     []string{"foo"},
  3337  								Path:     "foo",
  3338  								Protocol: "http",
  3339  								Interval: 1 * time.Second,
  3340  								Timeout:  1 * time.Second,
  3341  								Header: map[string][]string{
  3342  									"Foo": {"bar"},
  3343  								},
  3344  							},
  3345  							{
  3346  								Name:     "bar",
  3347  								Type:     "http",
  3348  								Command:  "foo",
  3349  								Args:     []string{"foo"},
  3350  								Path:     "foo",
  3351  								Protocol: "http",
  3352  								Interval: 1 * time.Second,
  3353  								Timeout:  1 * time.Second,
  3354  							},
  3355  							{
  3356  								Name:     "baz",
  3357  								Type:     "http",
  3358  								Command:  "foo",
  3359  								Args:     []string{"foo"},
  3360  								Path:     "foo",
  3361  								Protocol: "http",
  3362  								Interval: 1 * time.Second,
  3363  								Timeout:  1 * time.Second,
  3364  							},
  3365  						},
  3366  					},
  3367  				},
  3368  			},
  3369  			New: &Task{
  3370  				Services: []*Service{
  3371  					{
  3372  						Name: "foo",
  3373  						Checks: []*ServiceCheck{
  3374  							{
  3375  								Name:     "bar",
  3376  								Type:     "http",
  3377  								Command:  "foo",
  3378  								Args:     []string{"foo"},
  3379  								Path:     "foo",
  3380  								Protocol: "http",
  3381  								Interval: 1 * time.Second,
  3382  								Timeout:  1 * time.Second,
  3383  							},
  3384  							{
  3385  								Name:     "baz",
  3386  								Type:     "tcp",
  3387  								Command:  "foo",
  3388  								Args:     []string{"foo"},
  3389  								Path:     "foo",
  3390  								Protocol: "http",
  3391  								Interval: 1 * time.Second,
  3392  								Timeout:  1 * time.Second,
  3393  								Header: map[string][]string{
  3394  									"Eggs": {"spam"},
  3395  								},
  3396  							},
  3397  							{
  3398  								Name:     "bam",
  3399  								Type:     "http",
  3400  								Command:  "foo",
  3401  								Args:     []string{"foo"},
  3402  								Path:     "foo",
  3403  								Protocol: "http",
  3404  								Interval: 1 * time.Second,
  3405  								Timeout:  1 * time.Second,
  3406  							},
  3407  						},
  3408  					},
  3409  				},
  3410  			},
  3411  			Expected: &TaskDiff{
  3412  				Type: DiffTypeEdited,
  3413  				Objects: []*ObjectDiff{
  3414  					{
  3415  						Type: DiffTypeEdited,
  3416  						Name: "Service",
  3417  						Objects: []*ObjectDiff{
  3418  							{
  3419  								Type: DiffTypeEdited,
  3420  								Name: "Check",
  3421  								Fields: []*FieldDiff{
  3422  									{
  3423  										Type: DiffTypeEdited,
  3424  										Name: "Type",
  3425  										Old:  "http",
  3426  										New:  "tcp",
  3427  									},
  3428  								},
  3429  								Objects: []*ObjectDiff{
  3430  									{
  3431  										Type: DiffTypeAdded,
  3432  										Name: "Header",
  3433  										Fields: []*FieldDiff{
  3434  											{
  3435  												Type: DiffTypeAdded,
  3436  												Name: "Eggs[0]",
  3437  												Old:  "",
  3438  												New:  "spam",
  3439  											},
  3440  										},
  3441  									},
  3442  								},
  3443  							},
  3444  							{
  3445  								Type: DiffTypeAdded,
  3446  								Name: "Check",
  3447  								Fields: []*FieldDiff{
  3448  									{
  3449  										Type: DiffTypeAdded,
  3450  										Name: "Command",
  3451  										Old:  "",
  3452  										New:  "foo",
  3453  									},
  3454  									{
  3455  										Type: DiffTypeAdded,
  3456  										Name: "Interval",
  3457  										Old:  "",
  3458  										New:  "1000000000",
  3459  									},
  3460  									{
  3461  										Type: DiffTypeAdded,
  3462  										Name: "Name",
  3463  										Old:  "",
  3464  										New:  "bam",
  3465  									},
  3466  									{
  3467  										Type: DiffTypeAdded,
  3468  										Name: "Path",
  3469  										Old:  "",
  3470  										New:  "foo",
  3471  									},
  3472  									{
  3473  										Type: DiffTypeAdded,
  3474  										Name: "Protocol",
  3475  										Old:  "",
  3476  										New:  "http",
  3477  									},
  3478  									{
  3479  										Type: DiffTypeAdded,
  3480  										Name: "TLSSkipVerify",
  3481  										Old:  "",
  3482  										New:  "false",
  3483  									},
  3484  									{
  3485  										Type: DiffTypeAdded,
  3486  										Name: "Timeout",
  3487  										Old:  "",
  3488  										New:  "1000000000",
  3489  									},
  3490  									{
  3491  										Type: DiffTypeAdded,
  3492  										Name: "Type",
  3493  										Old:  "",
  3494  										New:  "http",
  3495  									},
  3496  								},
  3497  							},
  3498  							{
  3499  								Type: DiffTypeDeleted,
  3500  								Name: "Check",
  3501  								Fields: []*FieldDiff{
  3502  									{
  3503  										Type: DiffTypeDeleted,
  3504  										Name: "Command",
  3505  										Old:  "foo",
  3506  										New:  "",
  3507  									},
  3508  									{
  3509  										Type: DiffTypeDeleted,
  3510  										Name: "Interval",
  3511  										Old:  "1000000000",
  3512  										New:  "",
  3513  									},
  3514  									{
  3515  										Type: DiffTypeDeleted,
  3516  										Name: "Name",
  3517  										Old:  "foo",
  3518  										New:  "",
  3519  									},
  3520  									{
  3521  										Type: DiffTypeDeleted,
  3522  										Name: "Path",
  3523  										Old:  "foo",
  3524  										New:  "",
  3525  									},
  3526  									{
  3527  										Type: DiffTypeDeleted,
  3528  										Name: "Protocol",
  3529  										Old:  "http",
  3530  										New:  "",
  3531  									},
  3532  									{
  3533  										Type: DiffTypeDeleted,
  3534  										Name: "TLSSkipVerify",
  3535  										Old:  "false",
  3536  										New:  "",
  3537  									},
  3538  									{
  3539  										Type: DiffTypeDeleted,
  3540  										Name: "Timeout",
  3541  										Old:  "1000000000",
  3542  										New:  "",
  3543  									},
  3544  									{
  3545  										Type: DiffTypeDeleted,
  3546  										Name: "Type",
  3547  										Old:  "http",
  3548  										New:  "",
  3549  									},
  3550  								},
  3551  								Objects: []*ObjectDiff{
  3552  									{
  3553  										Type: DiffTypeDeleted,
  3554  										Name: "Header",
  3555  										Fields: []*FieldDiff{
  3556  											{
  3557  												Type: DiffTypeDeleted,
  3558  												Name: "Foo[0]",
  3559  												Old:  "bar",
  3560  											},
  3561  										},
  3562  									},
  3563  								},
  3564  							},
  3565  						},
  3566  					},
  3567  				},
  3568  			},
  3569  		},
  3570  		{
  3571  			Name:       "Service Checks edited with context",
  3572  			Contextual: true,
  3573  			Old: &Task{
  3574  				Services: []*Service{
  3575  					{
  3576  						Name: "foo",
  3577  						Checks: []*ServiceCheck{
  3578  							{
  3579  								Name:          "foo",
  3580  								Type:          "http",
  3581  								Command:       "foo",
  3582  								Args:          []string{"foo"},
  3583  								Path:          "foo",
  3584  								Protocol:      "http",
  3585  								Interval:      1 * time.Second,
  3586  								Timeout:       1 * time.Second,
  3587  								InitialStatus: "critical",
  3588  								Header: map[string][]string{
  3589  									"Foo": {"bar"},
  3590  								},
  3591  							},
  3592  						},
  3593  					},
  3594  				},
  3595  			},
  3596  			New: &Task{
  3597  				Services: []*Service{
  3598  					{
  3599  						Name: "foo",
  3600  						Checks: []*ServiceCheck{
  3601  							{
  3602  								Name:          "foo",
  3603  								Type:          "tcp",
  3604  								Command:       "foo",
  3605  								Args:          []string{"foo"},
  3606  								Path:          "foo",
  3607  								Protocol:      "http",
  3608  								Interval:      1 * time.Second,
  3609  								Timeout:       1 * time.Second,
  3610  								InitialStatus: "passing",
  3611  								Method:        "POST",
  3612  								Header: map[string][]string{
  3613  									"Foo":  {"bar", "baz"},
  3614  									"Eggs": {"spam"},
  3615  								},
  3616  							},
  3617  						},
  3618  					},
  3619  				},
  3620  			},
  3621  			Expected: &TaskDiff{
  3622  				Type: DiffTypeEdited,
  3623  				Objects: []*ObjectDiff{
  3624  					{
  3625  						Type: DiffTypeEdited,
  3626  						Name: "Service",
  3627  						Fields: []*FieldDiff{
  3628  							{
  3629  								Type: DiffTypeNone,
  3630  								Name: "AddressMode",
  3631  								Old:  "",
  3632  								New:  "",
  3633  							},
  3634  							{
  3635  								Type: DiffTypeNone,
  3636  								Name: "Name",
  3637  								Old:  "foo",
  3638  								New:  "foo",
  3639  							},
  3640  							{
  3641  								Type: DiffTypeNone,
  3642  								Name: "PortLabel",
  3643  								Old:  "",
  3644  								New:  "",
  3645  							},
  3646  						},
  3647  						Objects: []*ObjectDiff{
  3648  							{
  3649  								Type: DiffTypeEdited,
  3650  								Name: "Check",
  3651  								Fields: []*FieldDiff{
  3652  									{
  3653  										Type: DiffTypeNone,
  3654  										Name: "AddressMode",
  3655  										Old:  "",
  3656  										New:  "",
  3657  									},
  3658  									{
  3659  										Type: DiffTypeNone,
  3660  										Name: "Command",
  3661  										Old:  "foo",
  3662  										New:  "foo",
  3663  									},
  3664  									{
  3665  										Type: DiffTypeEdited,
  3666  										Name: "InitialStatus",
  3667  										Old:  "critical",
  3668  										New:  "passing",
  3669  									},
  3670  									{
  3671  										Type: DiffTypeNone,
  3672  										Name: "Interval",
  3673  										Old:  "1000000000",
  3674  										New:  "1000000000",
  3675  									},
  3676  									{
  3677  										Type: DiffTypeAdded,
  3678  										Name: "Method",
  3679  										Old:  "",
  3680  										New:  "POST",
  3681  									},
  3682  									{
  3683  										Type: DiffTypeNone,
  3684  										Name: "Name",
  3685  										Old:  "foo",
  3686  										New:  "foo",
  3687  									},
  3688  									{
  3689  										Type: DiffTypeNone,
  3690  										Name: "Path",
  3691  										Old:  "foo",
  3692  										New:  "foo",
  3693  									},
  3694  									{
  3695  										Type: DiffTypeNone,
  3696  										Name: "PortLabel",
  3697  										Old:  "",
  3698  										New:  "",
  3699  									},
  3700  									{
  3701  										Type: DiffTypeNone,
  3702  										Name: "Protocol",
  3703  										Old:  "http",
  3704  										New:  "http",
  3705  									},
  3706  									{
  3707  										Type: DiffTypeNone,
  3708  										Name: "TLSSkipVerify",
  3709  										Old:  "false",
  3710  										New:  "false",
  3711  									},
  3712  									{
  3713  										Type: DiffTypeNone,
  3714  										Name: "Timeout",
  3715  										Old:  "1000000000",
  3716  										New:  "1000000000",
  3717  									},
  3718  									{
  3719  										Type: DiffTypeEdited,
  3720  										Name: "Type",
  3721  										Old:  "http",
  3722  										New:  "tcp",
  3723  									},
  3724  								},
  3725  								Objects: []*ObjectDiff{
  3726  									{
  3727  										Type: DiffTypeEdited,
  3728  										Name: "Header",
  3729  										Fields: []*FieldDiff{
  3730  											{
  3731  												Type: DiffTypeAdded,
  3732  												Name: "Eggs[0]",
  3733  												Old:  "",
  3734  												New:  "spam",
  3735  											},
  3736  											{
  3737  												Type: DiffTypeNone,
  3738  												Name: "Foo[0]",
  3739  												Old:  "bar",
  3740  												New:  "bar",
  3741  											},
  3742  											{
  3743  												Type: DiffTypeAdded,
  3744  												Name: "Foo[1]",
  3745  												Old:  "",
  3746  												New:  "baz",
  3747  											},
  3748  										},
  3749  									},
  3750  								},
  3751  							},
  3752  						},
  3753  					},
  3754  				},
  3755  			},
  3756  		},
  3757  		{
  3758  			Name: "CheckRestart edited",
  3759  			Old: &Task{
  3760  				Services: []*Service{
  3761  					{
  3762  						Name: "foo",
  3763  						Checks: []*ServiceCheck{
  3764  							{
  3765  								Name:     "foo",
  3766  								Type:     "http",
  3767  								Command:  "foo",
  3768  								Args:     []string{"foo"},
  3769  								Path:     "foo",
  3770  								Protocol: "http",
  3771  								Interval: 1 * time.Second,
  3772  								Timeout:  1 * time.Second,
  3773  							},
  3774  							{
  3775  								Name:     "bar",
  3776  								Type:     "http",
  3777  								Command:  "foo",
  3778  								Args:     []string{"foo"},
  3779  								Path:     "foo",
  3780  								Protocol: "http",
  3781  								Interval: 1 * time.Second,
  3782  								Timeout:  1 * time.Second,
  3783  								CheckRestart: &CheckRestart{
  3784  									Limit: 2,
  3785  									Grace: 2 * time.Second,
  3786  								},
  3787  							},
  3788  							{
  3789  								Name:     "baz",
  3790  								Type:     "http",
  3791  								Command:  "foo",
  3792  								Args:     []string{"foo"},
  3793  								Path:     "foo",
  3794  								Protocol: "http",
  3795  								Interval: 1 * time.Second,
  3796  								Timeout:  1 * time.Second,
  3797  								CheckRestart: &CheckRestart{
  3798  									Limit: 3,
  3799  									Grace: 3 * time.Second,
  3800  								},
  3801  							},
  3802  						},
  3803  					},
  3804  				},
  3805  			},
  3806  			New: &Task{
  3807  				Services: []*Service{
  3808  					{
  3809  						Name: "foo",
  3810  						Checks: []*ServiceCheck{
  3811  							{
  3812  								Name:     "foo",
  3813  								Type:     "http",
  3814  								Command:  "foo",
  3815  								Args:     []string{"foo"},
  3816  								Path:     "foo",
  3817  								Protocol: "http",
  3818  								Interval: 1 * time.Second,
  3819  								Timeout:  1 * time.Second,
  3820  								CheckRestart: &CheckRestart{
  3821  									Limit: 1,
  3822  									Grace: 1 * time.Second,
  3823  								},
  3824  							},
  3825  							{
  3826  								Name:     "bar",
  3827  								Type:     "http",
  3828  								Command:  "foo",
  3829  								Args:     []string{"foo"},
  3830  								Path:     "foo",
  3831  								Protocol: "http",
  3832  								Interval: 1 * time.Second,
  3833  								Timeout:  1 * time.Second,
  3834  							},
  3835  							{
  3836  								Name:     "baz",
  3837  								Type:     "http",
  3838  								Command:  "foo",
  3839  								Args:     []string{"foo"},
  3840  								Path:     "foo",
  3841  								Protocol: "http",
  3842  								Interval: 1 * time.Second,
  3843  								Timeout:  1 * time.Second,
  3844  								CheckRestart: &CheckRestart{
  3845  									Limit: 4,
  3846  									Grace: 4 * time.Second,
  3847  								},
  3848  							},
  3849  						},
  3850  					},
  3851  				},
  3852  			},
  3853  			Expected: &TaskDiff{
  3854  				Type: DiffTypeEdited,
  3855  				Objects: []*ObjectDiff{
  3856  					{
  3857  						Type: DiffTypeEdited,
  3858  						Name: "Service",
  3859  						Objects: []*ObjectDiff{
  3860  							{
  3861  								Type: DiffTypeEdited,
  3862  								Name: "Check",
  3863  								Objects: []*ObjectDiff{
  3864  									{
  3865  										Type: DiffTypeEdited,
  3866  										Name: "CheckRestart",
  3867  										Fields: []*FieldDiff{
  3868  											{
  3869  												Type: DiffTypeEdited,
  3870  												Name: "Grace",
  3871  												Old:  "3000000000",
  3872  												New:  "4000000000",
  3873  											},
  3874  											{
  3875  												Type: DiffTypeEdited,
  3876  												Name: "Limit",
  3877  												Old:  "3",
  3878  												New:  "4",
  3879  											},
  3880  										},
  3881  									},
  3882  								},
  3883  							},
  3884  							{
  3885  								Type: DiffTypeEdited,
  3886  								Name: "Check",
  3887  								Objects: []*ObjectDiff{
  3888  									{
  3889  										Type: DiffTypeAdded,
  3890  										Name: "CheckRestart",
  3891  										Fields: []*FieldDiff{
  3892  											{
  3893  												Type: DiffTypeAdded,
  3894  												Name: "Grace",
  3895  												New:  "1000000000",
  3896  											},
  3897  											{
  3898  												Type: DiffTypeAdded,
  3899  												Name: "IgnoreWarnings",
  3900  												New:  "false",
  3901  											},
  3902  											{
  3903  												Type: DiffTypeAdded,
  3904  												Name: "Limit",
  3905  												New:  "1",
  3906  											},
  3907  										},
  3908  									},
  3909  								},
  3910  							},
  3911  							{
  3912  								Type: DiffTypeEdited,
  3913  								Name: "Check",
  3914  								Objects: []*ObjectDiff{
  3915  									{
  3916  										Type: DiffTypeDeleted,
  3917  										Name: "CheckRestart",
  3918  										Fields: []*FieldDiff{
  3919  											{
  3920  												Type: DiffTypeDeleted,
  3921  												Name: "Grace",
  3922  												Old:  "2000000000",
  3923  											},
  3924  											{
  3925  												Type: DiffTypeDeleted,
  3926  												Name: "IgnoreWarnings",
  3927  												Old:  "false",
  3928  											},
  3929  											{
  3930  												Type: DiffTypeDeleted,
  3931  												Name: "Limit",
  3932  												Old:  "2",
  3933  											},
  3934  										},
  3935  									},
  3936  								},
  3937  							},
  3938  						},
  3939  					},
  3940  				},
  3941  			},
  3942  		},
  3943  		{
  3944  			Name: "Vault added",
  3945  			Old:  &Task{},
  3946  			New: &Task{
  3947  				Vault: &Vault{
  3948  					Policies:     []string{"foo", "bar"},
  3949  					Env:          true,
  3950  					ChangeMode:   "signal",
  3951  					ChangeSignal: "SIGUSR1",
  3952  				},
  3953  			},
  3954  			Expected: &TaskDiff{
  3955  				Type: DiffTypeEdited,
  3956  				Objects: []*ObjectDiff{
  3957  					{
  3958  						Type: DiffTypeAdded,
  3959  						Name: "Vault",
  3960  						Fields: []*FieldDiff{
  3961  							{
  3962  								Type: DiffTypeAdded,
  3963  								Name: "ChangeMode",
  3964  								Old:  "",
  3965  								New:  "signal",
  3966  							},
  3967  							{
  3968  								Type: DiffTypeAdded,
  3969  								Name: "ChangeSignal",
  3970  								Old:  "",
  3971  								New:  "SIGUSR1",
  3972  							},
  3973  							{
  3974  								Type: DiffTypeAdded,
  3975  								Name: "Env",
  3976  								Old:  "",
  3977  								New:  "true",
  3978  							},
  3979  						},
  3980  						Objects: []*ObjectDiff{
  3981  							{
  3982  								Type: DiffTypeAdded,
  3983  								Name: "Policies",
  3984  								Fields: []*FieldDiff{
  3985  									{
  3986  										Type: DiffTypeAdded,
  3987  										Name: "Policies",
  3988  										Old:  "",
  3989  										New:  "bar",
  3990  									},
  3991  									{
  3992  										Type: DiffTypeAdded,
  3993  										Name: "Policies",
  3994  										Old:  "",
  3995  										New:  "foo",
  3996  									},
  3997  								},
  3998  							},
  3999  						},
  4000  					},
  4001  				},
  4002  			},
  4003  		},
  4004  		{
  4005  			Name: "Vault deleted",
  4006  			Old: &Task{
  4007  				Vault: &Vault{
  4008  					Policies:     []string{"foo", "bar"},
  4009  					Env:          true,
  4010  					ChangeMode:   "signal",
  4011  					ChangeSignal: "SIGUSR1",
  4012  				},
  4013  			},
  4014  			New: &Task{},
  4015  			Expected: &TaskDiff{
  4016  				Type: DiffTypeEdited,
  4017  				Objects: []*ObjectDiff{
  4018  					{
  4019  						Type: DiffTypeDeleted,
  4020  						Name: "Vault",
  4021  						Fields: []*FieldDiff{
  4022  							{
  4023  								Type: DiffTypeDeleted,
  4024  								Name: "ChangeMode",
  4025  								Old:  "signal",
  4026  								New:  "",
  4027  							},
  4028  							{
  4029  								Type: DiffTypeDeleted,
  4030  								Name: "ChangeSignal",
  4031  								Old:  "SIGUSR1",
  4032  								New:  "",
  4033  							},
  4034  							{
  4035  								Type: DiffTypeDeleted,
  4036  								Name: "Env",
  4037  								Old:  "true",
  4038  								New:  "",
  4039  							},
  4040  						},
  4041  						Objects: []*ObjectDiff{
  4042  							{
  4043  								Type: DiffTypeDeleted,
  4044  								Name: "Policies",
  4045  								Fields: []*FieldDiff{
  4046  									{
  4047  										Type: DiffTypeDeleted,
  4048  										Name: "Policies",
  4049  										Old:  "bar",
  4050  										New:  "",
  4051  									},
  4052  									{
  4053  										Type: DiffTypeDeleted,
  4054  										Name: "Policies",
  4055  										Old:  "foo",
  4056  										New:  "",
  4057  									},
  4058  								},
  4059  							},
  4060  						},
  4061  					},
  4062  				},
  4063  			},
  4064  		},
  4065  		{
  4066  			Name: "Vault edited",
  4067  			Old: &Task{
  4068  				Vault: &Vault{
  4069  					Policies:     []string{"foo", "bar"},
  4070  					Env:          true,
  4071  					ChangeMode:   "signal",
  4072  					ChangeSignal: "SIGUSR1",
  4073  				},
  4074  			},
  4075  			New: &Task{
  4076  				Vault: &Vault{
  4077  					Policies:     []string{"bar", "baz"},
  4078  					Env:          false,
  4079  					ChangeMode:   "restart",
  4080  					ChangeSignal: "foo",
  4081  				},
  4082  			},
  4083  			Expected: &TaskDiff{
  4084  				Type: DiffTypeEdited,
  4085  				Objects: []*ObjectDiff{
  4086  					{
  4087  						Type: DiffTypeEdited,
  4088  						Name: "Vault",
  4089  						Fields: []*FieldDiff{
  4090  							{
  4091  								Type: DiffTypeEdited,
  4092  								Name: "ChangeMode",
  4093  								Old:  "signal",
  4094  								New:  "restart",
  4095  							},
  4096  							{
  4097  								Type: DiffTypeEdited,
  4098  								Name: "ChangeSignal",
  4099  								Old:  "SIGUSR1",
  4100  								New:  "foo",
  4101  							},
  4102  							{
  4103  								Type: DiffTypeEdited,
  4104  								Name: "Env",
  4105  								Old:  "true",
  4106  								New:  "false",
  4107  							},
  4108  						},
  4109  						Objects: []*ObjectDiff{
  4110  							{
  4111  								Type: DiffTypeEdited,
  4112  								Name: "Policies",
  4113  								Fields: []*FieldDiff{
  4114  									{
  4115  										Type: DiffTypeAdded,
  4116  										Name: "Policies",
  4117  										Old:  "",
  4118  										New:  "baz",
  4119  									},
  4120  									{
  4121  										Type: DiffTypeDeleted,
  4122  										Name: "Policies",
  4123  										Old:  "foo",
  4124  										New:  "",
  4125  									},
  4126  								},
  4127  							},
  4128  						},
  4129  					},
  4130  				},
  4131  			},
  4132  		},
  4133  		{
  4134  			Name:       "Vault edited with context",
  4135  			Contextual: true,
  4136  			Old: &Task{
  4137  				Vault: &Vault{
  4138  					Policies:     []string{"foo", "bar"},
  4139  					Env:          true,
  4140  					ChangeMode:   "signal",
  4141  					ChangeSignal: "SIGUSR1",
  4142  				},
  4143  			},
  4144  			New: &Task{
  4145  				Vault: &Vault{
  4146  					Policies:     []string{"bar", "baz"},
  4147  					Env:          true,
  4148  					ChangeMode:   "signal",
  4149  					ChangeSignal: "SIGUSR1",
  4150  				},
  4151  			},
  4152  			Expected: &TaskDiff{
  4153  				Type: DiffTypeEdited,
  4154  				Objects: []*ObjectDiff{
  4155  					{
  4156  						Type: DiffTypeEdited,
  4157  						Name: "Vault",
  4158  						Fields: []*FieldDiff{
  4159  							{
  4160  								Type: DiffTypeNone,
  4161  								Name: "ChangeMode",
  4162  								Old:  "signal",
  4163  								New:  "signal",
  4164  							},
  4165  							{
  4166  								Type: DiffTypeNone,
  4167  								Name: "ChangeSignal",
  4168  								Old:  "SIGUSR1",
  4169  								New:  "SIGUSR1",
  4170  							},
  4171  							{
  4172  								Type: DiffTypeNone,
  4173  								Name: "Env",
  4174  								Old:  "true",
  4175  								New:  "true",
  4176  							},
  4177  						},
  4178  						Objects: []*ObjectDiff{
  4179  							{
  4180  								Type: DiffTypeEdited,
  4181  								Name: "Policies",
  4182  								Fields: []*FieldDiff{
  4183  									{
  4184  										Type: DiffTypeAdded,
  4185  										Name: "Policies",
  4186  										Old:  "",
  4187  										New:  "baz",
  4188  									},
  4189  									{
  4190  										Type: DiffTypeNone,
  4191  										Name: "Policies",
  4192  										Old:  "bar",
  4193  										New:  "bar",
  4194  									},
  4195  									{
  4196  										Type: DiffTypeDeleted,
  4197  										Name: "Policies",
  4198  										Old:  "foo",
  4199  										New:  "",
  4200  									},
  4201  								},
  4202  							},
  4203  						},
  4204  					},
  4205  				},
  4206  			},
  4207  		},
  4208  		{
  4209  			Name: "Template edited",
  4210  			Old: &Task{
  4211  				Templates: []*Template{
  4212  					{
  4213  						SourcePath:   "foo",
  4214  						DestPath:     "bar",
  4215  						EmbeddedTmpl: "baz",
  4216  						ChangeMode:   "bam",
  4217  						ChangeSignal: "SIGHUP",
  4218  						Splay:        1,
  4219  						Perms:        "0644",
  4220  						VaultGrace:   3 * time.Second,
  4221  					},
  4222  					{
  4223  						SourcePath:   "foo2",
  4224  						DestPath:     "bar2",
  4225  						EmbeddedTmpl: "baz2",
  4226  						ChangeMode:   "bam2",
  4227  						ChangeSignal: "SIGHUP2",
  4228  						Splay:        2,
  4229  						Perms:        "0666",
  4230  						Envvars:      true,
  4231  						VaultGrace:   5 * time.Second,
  4232  					},
  4233  				},
  4234  			},
  4235  			New: &Task{
  4236  				Templates: []*Template{
  4237  					{
  4238  						SourcePath:   "foo",
  4239  						DestPath:     "bar",
  4240  						EmbeddedTmpl: "baz",
  4241  						ChangeMode:   "bam",
  4242  						ChangeSignal: "SIGHUP",
  4243  						Splay:        1,
  4244  						Perms:        "0644",
  4245  						VaultGrace:   3 * time.Second,
  4246  					},
  4247  					{
  4248  						SourcePath:   "foo3",
  4249  						DestPath:     "bar3",
  4250  						EmbeddedTmpl: "baz3",
  4251  						ChangeMode:   "bam3",
  4252  						ChangeSignal: "SIGHUP3",
  4253  						Splay:        3,
  4254  						Perms:        "0776",
  4255  						VaultGrace:   10 * time.Second,
  4256  					},
  4257  				},
  4258  			},
  4259  			Expected: &TaskDiff{
  4260  				Type: DiffTypeEdited,
  4261  				Objects: []*ObjectDiff{
  4262  					{
  4263  						Type: DiffTypeAdded,
  4264  						Name: "Template",
  4265  						Fields: []*FieldDiff{
  4266  							{
  4267  								Type: DiffTypeAdded,
  4268  								Name: "ChangeMode",
  4269  								Old:  "",
  4270  								New:  "bam3",
  4271  							},
  4272  							{
  4273  								Type: DiffTypeAdded,
  4274  								Name: "ChangeSignal",
  4275  								Old:  "",
  4276  								New:  "SIGHUP3",
  4277  							},
  4278  							{
  4279  								Type: DiffTypeAdded,
  4280  								Name: "DestPath",
  4281  								Old:  "",
  4282  								New:  "bar3",
  4283  							},
  4284  							{
  4285  								Type: DiffTypeAdded,
  4286  								Name: "EmbeddedTmpl",
  4287  								Old:  "",
  4288  								New:  "baz3",
  4289  							},
  4290  							{
  4291  								Type: DiffTypeAdded,
  4292  								Name: "Envvars",
  4293  								Old:  "",
  4294  								New:  "false",
  4295  							},
  4296  							{
  4297  								Type: DiffTypeAdded,
  4298  								Name: "Perms",
  4299  								Old:  "",
  4300  								New:  "0776",
  4301  							},
  4302  							{
  4303  								Type: DiffTypeAdded,
  4304  								Name: "SourcePath",
  4305  								Old:  "",
  4306  								New:  "foo3",
  4307  							},
  4308  							{
  4309  								Type: DiffTypeAdded,
  4310  								Name: "Splay",
  4311  								Old:  "",
  4312  								New:  "3",
  4313  							},
  4314  							{
  4315  								Type: DiffTypeAdded,
  4316  								Name: "VaultGrace",
  4317  								Old:  "",
  4318  								New:  "10000000000",
  4319  							},
  4320  						},
  4321  					},
  4322  					{
  4323  						Type: DiffTypeDeleted,
  4324  						Name: "Template",
  4325  						Fields: []*FieldDiff{
  4326  							{
  4327  								Type: DiffTypeDeleted,
  4328  								Name: "ChangeMode",
  4329  								Old:  "bam2",
  4330  								New:  "",
  4331  							},
  4332  							{
  4333  								Type: DiffTypeDeleted,
  4334  								Name: "ChangeSignal",
  4335  								Old:  "SIGHUP2",
  4336  								New:  "",
  4337  							},
  4338  							{
  4339  								Type: DiffTypeDeleted,
  4340  								Name: "DestPath",
  4341  								Old:  "bar2",
  4342  								New:  "",
  4343  							},
  4344  							{
  4345  								Type: DiffTypeDeleted,
  4346  								Name: "EmbeddedTmpl",
  4347  								Old:  "baz2",
  4348  								New:  "",
  4349  							},
  4350  							{
  4351  								Type: DiffTypeDeleted,
  4352  								Name: "Envvars",
  4353  								Old:  "true",
  4354  								New:  "",
  4355  							},
  4356  							{
  4357  								Type: DiffTypeDeleted,
  4358  								Name: "Perms",
  4359  								Old:  "0666",
  4360  								New:  "",
  4361  							},
  4362  							{
  4363  								Type: DiffTypeDeleted,
  4364  								Name: "SourcePath",
  4365  								Old:  "foo2",
  4366  								New:  "",
  4367  							},
  4368  							{
  4369  								Type: DiffTypeDeleted,
  4370  								Name: "Splay",
  4371  								Old:  "2",
  4372  								New:  "",
  4373  							},
  4374  							{
  4375  								Type: DiffTypeDeleted,
  4376  								Name: "VaultGrace",
  4377  								Old:  "5000000000",
  4378  								New:  "",
  4379  							},
  4380  						},
  4381  					},
  4382  				},
  4383  			},
  4384  		},
  4385  		{
  4386  			Name: "DispatchPayload added",
  4387  			Old:  &Task{},
  4388  			New: &Task{
  4389  				DispatchPayload: &DispatchPayloadConfig{
  4390  					File: "foo",
  4391  				},
  4392  			},
  4393  			Expected: &TaskDiff{
  4394  				Type: DiffTypeEdited,
  4395  				Objects: []*ObjectDiff{
  4396  					{
  4397  						Type: DiffTypeAdded,
  4398  						Name: "DispatchPayload",
  4399  						Fields: []*FieldDiff{
  4400  							{
  4401  								Type: DiffTypeAdded,
  4402  								Name: "File",
  4403  								Old:  "",
  4404  								New:  "foo",
  4405  							},
  4406  						},
  4407  					},
  4408  				},
  4409  			},
  4410  		},
  4411  		{
  4412  			Name: "DispatchPayload deleted",
  4413  			Old: &Task{
  4414  				DispatchPayload: &DispatchPayloadConfig{
  4415  					File: "foo",
  4416  				},
  4417  			},
  4418  			New: &Task{},
  4419  			Expected: &TaskDiff{
  4420  				Type: DiffTypeEdited,
  4421  				Objects: []*ObjectDiff{
  4422  					{
  4423  						Type: DiffTypeDeleted,
  4424  						Name: "DispatchPayload",
  4425  						Fields: []*FieldDiff{
  4426  							{
  4427  								Type: DiffTypeDeleted,
  4428  								Name: "File",
  4429  								Old:  "foo",
  4430  								New:  "",
  4431  							},
  4432  						},
  4433  					},
  4434  				},
  4435  			},
  4436  		},
  4437  		{
  4438  			Name: "Dispatch payload edited",
  4439  			Old: &Task{
  4440  				DispatchPayload: &DispatchPayloadConfig{
  4441  					File: "foo",
  4442  				},
  4443  			},
  4444  			New: &Task{
  4445  				DispatchPayload: &DispatchPayloadConfig{
  4446  					File: "bar",
  4447  				},
  4448  			},
  4449  			Expected: &TaskDiff{
  4450  				Type: DiffTypeEdited,
  4451  				Objects: []*ObjectDiff{
  4452  					{
  4453  						Type: DiffTypeEdited,
  4454  						Name: "DispatchPayload",
  4455  						Fields: []*FieldDiff{
  4456  							{
  4457  								Type: DiffTypeEdited,
  4458  								Name: "File",
  4459  								Old:  "foo",
  4460  								New:  "bar",
  4461  							},
  4462  						},
  4463  					},
  4464  				},
  4465  			},
  4466  		},
  4467  		{
  4468  			// Place holder for if more fields are added
  4469  			Name:       "DispatchPayload edited with context",
  4470  			Contextual: true,
  4471  			Old: &Task{
  4472  				DispatchPayload: &DispatchPayloadConfig{
  4473  					File: "foo",
  4474  				},
  4475  			},
  4476  			New: &Task{
  4477  				DispatchPayload: &DispatchPayloadConfig{
  4478  					File: "bar",
  4479  				},
  4480  			},
  4481  			Expected: &TaskDiff{
  4482  				Type: DiffTypeEdited,
  4483  				Objects: []*ObjectDiff{
  4484  					{
  4485  						Type: DiffTypeEdited,
  4486  						Name: "DispatchPayload",
  4487  						Fields: []*FieldDiff{
  4488  							{
  4489  								Type: DiffTypeEdited,
  4490  								Name: "File",
  4491  								Old:  "foo",
  4492  								New:  "bar",
  4493  							},
  4494  						},
  4495  					},
  4496  				},
  4497  			},
  4498  		},
  4499  	}
  4500  
  4501  	for i, c := range cases {
  4502  		t.Run(c.Name, func(t *testing.T) {
  4503  			actual, err := c.Old.Diff(c.New, c.Contextual)
  4504  			if c.Error && err == nil {
  4505  				t.Fatalf("case %d: expected errored", i+1)
  4506  			} else if err != nil {
  4507  				if !c.Error {
  4508  					t.Fatalf("case %d: errored %#v", i+1, err)
  4509  				} else {
  4510  					return
  4511  				}
  4512  			}
  4513  
  4514  			if !reflect.DeepEqual(actual, c.Expected) {
  4515  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  4516  					i+1, actual, c.Expected)
  4517  			}
  4518  		})
  4519  	}
  4520  }