github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/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  			// Update strategy deleted
  1499  			Old: &TaskGroup{
  1500  				Update: &UpdateStrategy{
  1501  					AutoRevert: true,
  1502  				},
  1503  			},
  1504  			New: &TaskGroup{},
  1505  			Expected: &TaskGroupDiff{
  1506  				Type: DiffTypeEdited,
  1507  				Objects: []*ObjectDiff{
  1508  					{
  1509  						Type: DiffTypeDeleted,
  1510  						Name: "Update",
  1511  						Fields: []*FieldDiff{
  1512  							{
  1513  								Type: DiffTypeDeleted,
  1514  								Name: "AutoRevert",
  1515  								Old:  "true",
  1516  								New:  "",
  1517  							},
  1518  							{
  1519  								Type: DiffTypeDeleted,
  1520  								Name: "Canary",
  1521  								Old:  "0",
  1522  								New:  "",
  1523  							},
  1524  							{
  1525  								Type: DiffTypeDeleted,
  1526  								Name: "HealthyDeadline",
  1527  								Old:  "0",
  1528  								New:  "",
  1529  							},
  1530  							{
  1531  								Type: DiffTypeDeleted,
  1532  								Name: "MaxParallel",
  1533  								Old:  "0",
  1534  								New:  "",
  1535  							},
  1536  							{
  1537  								Type: DiffTypeDeleted,
  1538  								Name: "MinHealthyTime",
  1539  								Old:  "0",
  1540  								New:  "",
  1541  							},
  1542  						},
  1543  					},
  1544  				},
  1545  			},
  1546  		},
  1547  		{
  1548  			// Update strategy added
  1549  			Old: &TaskGroup{},
  1550  			New: &TaskGroup{
  1551  				Update: &UpdateStrategy{
  1552  					AutoRevert: true,
  1553  				},
  1554  			},
  1555  			Expected: &TaskGroupDiff{
  1556  				Type: DiffTypeEdited,
  1557  				Objects: []*ObjectDiff{
  1558  					{
  1559  						Type: DiffTypeAdded,
  1560  						Name: "Update",
  1561  						Fields: []*FieldDiff{
  1562  							{
  1563  								Type: DiffTypeAdded,
  1564  								Name: "AutoRevert",
  1565  								Old:  "",
  1566  								New:  "true",
  1567  							},
  1568  							{
  1569  								Type: DiffTypeAdded,
  1570  								Name: "Canary",
  1571  								Old:  "",
  1572  								New:  "0",
  1573  							},
  1574  							{
  1575  								Type: DiffTypeAdded,
  1576  								Name: "HealthyDeadline",
  1577  								Old:  "",
  1578  								New:  "0",
  1579  							},
  1580  							{
  1581  								Type: DiffTypeAdded,
  1582  								Name: "MaxParallel",
  1583  								Old:  "",
  1584  								New:  "0",
  1585  							},
  1586  							{
  1587  								Type: DiffTypeAdded,
  1588  								Name: "MinHealthyTime",
  1589  								Old:  "",
  1590  								New:  "0",
  1591  							},
  1592  						},
  1593  					},
  1594  				},
  1595  			},
  1596  		},
  1597  		{
  1598  			// Update strategy edited
  1599  			Old: &TaskGroup{
  1600  				Update: &UpdateStrategy{
  1601  					MaxParallel:     5,
  1602  					HealthCheck:     "foo",
  1603  					MinHealthyTime:  1 * time.Second,
  1604  					HealthyDeadline: 30 * time.Second,
  1605  					AutoRevert:      true,
  1606  					Canary:          2,
  1607  				},
  1608  			},
  1609  			New: &TaskGroup{
  1610  				Update: &UpdateStrategy{
  1611  					MaxParallel:     7,
  1612  					HealthCheck:     "bar",
  1613  					MinHealthyTime:  2 * time.Second,
  1614  					HealthyDeadline: 31 * time.Second,
  1615  					AutoRevert:      false,
  1616  					Canary:          1,
  1617  				},
  1618  			},
  1619  			Expected: &TaskGroupDiff{
  1620  				Type: DiffTypeEdited,
  1621  				Objects: []*ObjectDiff{
  1622  					{
  1623  						Type: DiffTypeEdited,
  1624  						Name: "Update",
  1625  						Fields: []*FieldDiff{
  1626  							{
  1627  								Type: DiffTypeEdited,
  1628  								Name: "AutoRevert",
  1629  								Old:  "true",
  1630  								New:  "false",
  1631  							},
  1632  							{
  1633  								Type: DiffTypeEdited,
  1634  								Name: "Canary",
  1635  								Old:  "2",
  1636  								New:  "1",
  1637  							},
  1638  							{
  1639  								Type: DiffTypeEdited,
  1640  								Name: "HealthCheck",
  1641  								Old:  "foo",
  1642  								New:  "bar",
  1643  							},
  1644  							{
  1645  								Type: DiffTypeEdited,
  1646  								Name: "HealthyDeadline",
  1647  								Old:  "30000000000",
  1648  								New:  "31000000000",
  1649  							},
  1650  							{
  1651  								Type: DiffTypeEdited,
  1652  								Name: "MaxParallel",
  1653  								Old:  "5",
  1654  								New:  "7",
  1655  							},
  1656  							{
  1657  								Type: DiffTypeEdited,
  1658  								Name: "MinHealthyTime",
  1659  								Old:  "1000000000",
  1660  								New:  "2000000000",
  1661  							},
  1662  						},
  1663  					},
  1664  				},
  1665  			},
  1666  		},
  1667  		{
  1668  			// Update strategy edited with context
  1669  			Contextual: true,
  1670  			Old: &TaskGroup{
  1671  				Update: &UpdateStrategy{
  1672  					MaxParallel:     5,
  1673  					HealthCheck:     "foo",
  1674  					MinHealthyTime:  1 * time.Second,
  1675  					HealthyDeadline: 30 * time.Second,
  1676  					AutoRevert:      true,
  1677  					Canary:          2,
  1678  				},
  1679  			},
  1680  			New: &TaskGroup{
  1681  				Update: &UpdateStrategy{
  1682  					MaxParallel:     7,
  1683  					HealthCheck:     "foo",
  1684  					MinHealthyTime:  1 * time.Second,
  1685  					HealthyDeadline: 30 * time.Second,
  1686  					AutoRevert:      true,
  1687  					Canary:          2,
  1688  				},
  1689  			},
  1690  			Expected: &TaskGroupDiff{
  1691  				Type: DiffTypeEdited,
  1692  				Objects: []*ObjectDiff{
  1693  					{
  1694  						Type: DiffTypeEdited,
  1695  						Name: "Update",
  1696  						Fields: []*FieldDiff{
  1697  							{
  1698  								Type: DiffTypeNone,
  1699  								Name: "AutoRevert",
  1700  								Old:  "true",
  1701  								New:  "true",
  1702  							},
  1703  							{
  1704  								Type: DiffTypeNone,
  1705  								Name: "Canary",
  1706  								Old:  "2",
  1707  								New:  "2",
  1708  							},
  1709  							{
  1710  								Type: DiffTypeNone,
  1711  								Name: "HealthCheck",
  1712  								Old:  "foo",
  1713  								New:  "foo",
  1714  							},
  1715  							{
  1716  								Type: DiffTypeNone,
  1717  								Name: "HealthyDeadline",
  1718  								Old:  "30000000000",
  1719  								New:  "30000000000",
  1720  							},
  1721  							{
  1722  								Type: DiffTypeEdited,
  1723  								Name: "MaxParallel",
  1724  								Old:  "5",
  1725  								New:  "7",
  1726  							},
  1727  							{
  1728  								Type: DiffTypeNone,
  1729  								Name: "MinHealthyTime",
  1730  								Old:  "1000000000",
  1731  								New:  "1000000000",
  1732  							},
  1733  						},
  1734  					},
  1735  				},
  1736  			},
  1737  		},
  1738  		{
  1739  			// EphemeralDisk added
  1740  			Old: &TaskGroup{},
  1741  			New: &TaskGroup{
  1742  				EphemeralDisk: &EphemeralDisk{
  1743  					Migrate: true,
  1744  					Sticky:  true,
  1745  					SizeMB:  100,
  1746  				},
  1747  			},
  1748  			Expected: &TaskGroupDiff{
  1749  				Type: DiffTypeEdited,
  1750  				Objects: []*ObjectDiff{
  1751  					{
  1752  						Type: DiffTypeAdded,
  1753  						Name: "EphemeralDisk",
  1754  						Fields: []*FieldDiff{
  1755  							{
  1756  								Type: DiffTypeAdded,
  1757  								Name: "Migrate",
  1758  								Old:  "",
  1759  								New:  "true",
  1760  							},
  1761  							{
  1762  								Type: DiffTypeAdded,
  1763  								Name: "SizeMB",
  1764  								Old:  "",
  1765  								New:  "100",
  1766  							},
  1767  							{
  1768  								Type: DiffTypeAdded,
  1769  								Name: "Sticky",
  1770  								Old:  "",
  1771  								New:  "true",
  1772  							},
  1773  						},
  1774  					},
  1775  				},
  1776  			},
  1777  		},
  1778  		{
  1779  			// EphemeralDisk deleted
  1780  			Old: &TaskGroup{
  1781  				EphemeralDisk: &EphemeralDisk{
  1782  					Migrate: true,
  1783  					Sticky:  true,
  1784  					SizeMB:  100,
  1785  				},
  1786  			},
  1787  			New: &TaskGroup{},
  1788  			Expected: &TaskGroupDiff{
  1789  				Type: DiffTypeEdited,
  1790  				Objects: []*ObjectDiff{
  1791  					{
  1792  						Type: DiffTypeDeleted,
  1793  						Name: "EphemeralDisk",
  1794  						Fields: []*FieldDiff{
  1795  							{
  1796  								Type: DiffTypeDeleted,
  1797  								Name: "Migrate",
  1798  								Old:  "true",
  1799  								New:  "",
  1800  							},
  1801  							{
  1802  								Type: DiffTypeDeleted,
  1803  								Name: "SizeMB",
  1804  								Old:  "100",
  1805  								New:  "",
  1806  							},
  1807  							{
  1808  								Type: DiffTypeDeleted,
  1809  								Name: "Sticky",
  1810  								Old:  "true",
  1811  								New:  "",
  1812  							},
  1813  						},
  1814  					},
  1815  				},
  1816  			},
  1817  		},
  1818  		{
  1819  			// EphemeralDisk edited
  1820  			Old: &TaskGroup{
  1821  				EphemeralDisk: &EphemeralDisk{
  1822  					Migrate: true,
  1823  					Sticky:  true,
  1824  					SizeMB:  150,
  1825  				},
  1826  			},
  1827  			New: &TaskGroup{
  1828  				EphemeralDisk: &EphemeralDisk{
  1829  					Migrate: false,
  1830  					Sticky:  false,
  1831  					SizeMB:  90,
  1832  				},
  1833  			},
  1834  			Expected: &TaskGroupDiff{
  1835  				Type: DiffTypeEdited,
  1836  				Objects: []*ObjectDiff{
  1837  					{
  1838  						Type: DiffTypeEdited,
  1839  						Name: "EphemeralDisk",
  1840  						Fields: []*FieldDiff{
  1841  							{
  1842  								Type: DiffTypeEdited,
  1843  								Name: "Migrate",
  1844  								Old:  "true",
  1845  								New:  "false",
  1846  							},
  1847  							{
  1848  								Type: DiffTypeEdited,
  1849  								Name: "SizeMB",
  1850  								Old:  "150",
  1851  								New:  "90",
  1852  							},
  1853  
  1854  							{
  1855  								Type: DiffTypeEdited,
  1856  								Name: "Sticky",
  1857  								Old:  "true",
  1858  								New:  "false",
  1859  							},
  1860  						},
  1861  					},
  1862  				},
  1863  			},
  1864  		},
  1865  		{
  1866  			// EphemeralDisk edited with context
  1867  			Contextual: true,
  1868  			Old: &TaskGroup{
  1869  				EphemeralDisk: &EphemeralDisk{
  1870  					Migrate: false,
  1871  					Sticky:  false,
  1872  					SizeMB:  100,
  1873  				},
  1874  			},
  1875  			New: &TaskGroup{
  1876  				EphemeralDisk: &EphemeralDisk{
  1877  					Migrate: true,
  1878  					Sticky:  true,
  1879  					SizeMB:  90,
  1880  				},
  1881  			},
  1882  			Expected: &TaskGroupDiff{
  1883  				Type: DiffTypeEdited,
  1884  				Objects: []*ObjectDiff{
  1885  					{
  1886  						Type: DiffTypeEdited,
  1887  						Name: "EphemeralDisk",
  1888  						Fields: []*FieldDiff{
  1889  							{
  1890  								Type: DiffTypeEdited,
  1891  								Name: "Migrate",
  1892  								Old:  "false",
  1893  								New:  "true",
  1894  							},
  1895  							{
  1896  								Type: DiffTypeEdited,
  1897  								Name: "SizeMB",
  1898  								Old:  "100",
  1899  								New:  "90",
  1900  							},
  1901  							{
  1902  								Type: DiffTypeEdited,
  1903  								Name: "Sticky",
  1904  								Old:  "false",
  1905  								New:  "true",
  1906  							},
  1907  						},
  1908  					},
  1909  				},
  1910  			},
  1911  		},
  1912  		{
  1913  			// Tasks edited
  1914  			Old: &TaskGroup{
  1915  				Tasks: []*Task{
  1916  					{
  1917  						Name:   "foo",
  1918  						Driver: "docker",
  1919  					},
  1920  					{
  1921  						Name:   "bar",
  1922  						Driver: "docker",
  1923  					},
  1924  					{
  1925  						Name:          "baz",
  1926  						ShutdownDelay: 1 * time.Second,
  1927  					},
  1928  				},
  1929  			},
  1930  			New: &TaskGroup{
  1931  				Tasks: []*Task{
  1932  					{
  1933  						Name:   "bar",
  1934  						Driver: "docker",
  1935  					},
  1936  					{
  1937  						Name:   "bam",
  1938  						Driver: "docker",
  1939  					},
  1940  					{
  1941  						Name:          "baz",
  1942  						ShutdownDelay: 2 * time.Second,
  1943  					},
  1944  				},
  1945  			},
  1946  			Expected: &TaskGroupDiff{
  1947  				Type: DiffTypeEdited,
  1948  				Tasks: []*TaskDiff{
  1949  					{
  1950  						Type: DiffTypeAdded,
  1951  						Name: "bam",
  1952  						Fields: []*FieldDiff{
  1953  							{
  1954  								Type: DiffTypeAdded,
  1955  								Name: "Driver",
  1956  								Old:  "",
  1957  								New:  "docker",
  1958  							},
  1959  							{
  1960  								Type: DiffTypeAdded,
  1961  								Name: "KillTimeout",
  1962  								Old:  "",
  1963  								New:  "0",
  1964  							},
  1965  							{
  1966  								Type: DiffTypeAdded,
  1967  								Name: "Leader",
  1968  								Old:  "",
  1969  								New:  "false",
  1970  							},
  1971  							{
  1972  								Type: DiffTypeAdded,
  1973  								Name: "ShutdownDelay",
  1974  								Old:  "",
  1975  								New:  "0",
  1976  							},
  1977  						},
  1978  					},
  1979  					{
  1980  						Type: DiffTypeNone,
  1981  						Name: "bar",
  1982  					},
  1983  					{
  1984  						Type: DiffTypeEdited,
  1985  						Name: "baz",
  1986  						Fields: []*FieldDiff{
  1987  							{
  1988  								Type: DiffTypeEdited,
  1989  								Name: "ShutdownDelay",
  1990  								Old:  "1000000000",
  1991  								New:  "2000000000",
  1992  							},
  1993  						},
  1994  					},
  1995  					{
  1996  						Type: DiffTypeDeleted,
  1997  						Name: "foo",
  1998  						Fields: []*FieldDiff{
  1999  							{
  2000  								Type: DiffTypeDeleted,
  2001  								Name: "Driver",
  2002  								Old:  "docker",
  2003  								New:  "",
  2004  							},
  2005  							{
  2006  								Type: DiffTypeDeleted,
  2007  								Name: "KillTimeout",
  2008  								Old:  "0",
  2009  								New:  "",
  2010  							},
  2011  							{
  2012  								Type: DiffTypeDeleted,
  2013  								Name: "Leader",
  2014  								Old:  "false",
  2015  								New:  "",
  2016  							},
  2017  							{
  2018  								Type: DiffTypeDeleted,
  2019  								Name: "ShutdownDelay",
  2020  								Old:  "0",
  2021  								New:  "",
  2022  							},
  2023  						},
  2024  					},
  2025  				},
  2026  			},
  2027  		},
  2028  	}
  2029  
  2030  	for i, c := range cases {
  2031  		actual, err := c.Old.Diff(c.New, c.Contextual)
  2032  		if c.Error && err == nil {
  2033  			t.Fatalf("case %d: expected errored", i+1)
  2034  		} else if err != nil {
  2035  			if !c.Error {
  2036  				t.Fatalf("case %d: errored %#v", i+1, err)
  2037  			} else {
  2038  				continue
  2039  			}
  2040  		}
  2041  
  2042  		if !reflect.DeepEqual(actual, c.Expected) {
  2043  			t.Fatalf("case %d: got:\n%#v\n want:\n%#v\n",
  2044  				i+1, actual, c.Expected)
  2045  		}
  2046  	}
  2047  }
  2048  
  2049  func TestTaskDiff(t *testing.T) {
  2050  	cases := []struct {
  2051  		Name       string
  2052  		Old, New   *Task
  2053  		Expected   *TaskDiff
  2054  		Error      bool
  2055  		Contextual bool
  2056  	}{
  2057  		{
  2058  			Name: "Empty",
  2059  			Old:  nil,
  2060  			New:  nil,
  2061  			Expected: &TaskDiff{
  2062  				Type: DiffTypeNone,
  2063  			},
  2064  		},
  2065  		{
  2066  			Name: "Primitive only that has different names",
  2067  			Old: &Task{
  2068  				Name: "foo",
  2069  				Meta: map[string]string{
  2070  					"foo": "bar",
  2071  				},
  2072  			},
  2073  			New: &Task{
  2074  				Name: "bar",
  2075  				Meta: map[string]string{
  2076  					"foo": "bar",
  2077  				},
  2078  			},
  2079  			Error: true,
  2080  		},
  2081  		{
  2082  			Name: "Primitive only that is the same",
  2083  			Old: &Task{
  2084  				Name:   "foo",
  2085  				Driver: "exec",
  2086  				User:   "foo",
  2087  				Env: map[string]string{
  2088  					"FOO": "bar",
  2089  				},
  2090  				Meta: map[string]string{
  2091  					"foo": "bar",
  2092  				},
  2093  				KillTimeout: 1 * time.Second,
  2094  				Leader:      true,
  2095  			},
  2096  			New: &Task{
  2097  				Name:   "foo",
  2098  				Driver: "exec",
  2099  				User:   "foo",
  2100  				Env: map[string]string{
  2101  					"FOO": "bar",
  2102  				},
  2103  				Meta: map[string]string{
  2104  					"foo": "bar",
  2105  				},
  2106  				KillTimeout: 1 * time.Second,
  2107  				Leader:      true,
  2108  			},
  2109  			Expected: &TaskDiff{
  2110  				Type: DiffTypeNone,
  2111  				Name: "foo",
  2112  			},
  2113  		},
  2114  		{
  2115  			Name: "Primitive only that has diffs",
  2116  			Old: &Task{
  2117  				Name:   "foo",
  2118  				Driver: "exec",
  2119  				User:   "foo",
  2120  				Env: map[string]string{
  2121  					"FOO": "bar",
  2122  				},
  2123  				Meta: map[string]string{
  2124  					"foo": "bar",
  2125  				},
  2126  				KillTimeout: 1 * time.Second,
  2127  				Leader:      true,
  2128  			},
  2129  			New: &Task{
  2130  				Name:   "foo",
  2131  				Driver: "docker",
  2132  				User:   "bar",
  2133  				Env: map[string]string{
  2134  					"FOO": "baz",
  2135  				},
  2136  				Meta: map[string]string{
  2137  					"foo": "baz",
  2138  				},
  2139  				KillTimeout: 2 * time.Second,
  2140  				Leader:      false,
  2141  			},
  2142  			Expected: &TaskDiff{
  2143  				Type: DiffTypeEdited,
  2144  				Name: "foo",
  2145  				Fields: []*FieldDiff{
  2146  					{
  2147  						Type: DiffTypeEdited,
  2148  						Name: "Driver",
  2149  						Old:  "exec",
  2150  						New:  "docker",
  2151  					},
  2152  					{
  2153  						Type: DiffTypeEdited,
  2154  						Name: "Env[FOO]",
  2155  						Old:  "bar",
  2156  						New:  "baz",
  2157  					},
  2158  					{
  2159  						Type: DiffTypeEdited,
  2160  						Name: "KillTimeout",
  2161  						Old:  "1000000000",
  2162  						New:  "2000000000",
  2163  					},
  2164  					{
  2165  						Type: DiffTypeEdited,
  2166  						Name: "Leader",
  2167  						Old:  "true",
  2168  						New:  "false",
  2169  					},
  2170  					{
  2171  						Type: DiffTypeEdited,
  2172  						Name: "Meta[foo]",
  2173  						Old:  "bar",
  2174  						New:  "baz",
  2175  					},
  2176  					{
  2177  						Type: DiffTypeEdited,
  2178  						Name: "User",
  2179  						Old:  "foo",
  2180  						New:  "bar",
  2181  					},
  2182  				},
  2183  			},
  2184  		},
  2185  		{
  2186  			Name: "Map diff",
  2187  			Old: &Task{
  2188  				Meta: map[string]string{
  2189  					"foo": "foo",
  2190  					"bar": "bar",
  2191  				},
  2192  				Env: map[string]string{
  2193  					"foo": "foo",
  2194  					"bar": "bar",
  2195  				},
  2196  			},
  2197  			New: &Task{
  2198  				Meta: map[string]string{
  2199  					"bar": "bar",
  2200  					"baz": "baz",
  2201  				},
  2202  				Env: map[string]string{
  2203  					"bar": "bar",
  2204  					"baz": "baz",
  2205  				},
  2206  			},
  2207  			Expected: &TaskDiff{
  2208  				Type: DiffTypeEdited,
  2209  				Fields: []*FieldDiff{
  2210  					{
  2211  						Type: DiffTypeAdded,
  2212  						Name: "Env[baz]",
  2213  						Old:  "",
  2214  						New:  "baz",
  2215  					},
  2216  					{
  2217  						Type: DiffTypeDeleted,
  2218  						Name: "Env[foo]",
  2219  						Old:  "foo",
  2220  						New:  "",
  2221  					},
  2222  					{
  2223  						Type: DiffTypeAdded,
  2224  						Name: "Meta[baz]",
  2225  						Old:  "",
  2226  						New:  "baz",
  2227  					},
  2228  					{
  2229  						Type: DiffTypeDeleted,
  2230  						Name: "Meta[foo]",
  2231  						Old:  "foo",
  2232  						New:  "",
  2233  					},
  2234  				},
  2235  			},
  2236  		},
  2237  		{
  2238  			Name: "Constraints edited",
  2239  			Old: &Task{
  2240  				Constraints: []*Constraint{
  2241  					{
  2242  						LTarget: "foo",
  2243  						RTarget: "foo",
  2244  						Operand: "foo",
  2245  						str:     "foo",
  2246  					},
  2247  					{
  2248  						LTarget: "bar",
  2249  						RTarget: "bar",
  2250  						Operand: "bar",
  2251  						str:     "bar",
  2252  					},
  2253  				},
  2254  			},
  2255  			New: &Task{
  2256  				Constraints: []*Constraint{
  2257  					{
  2258  						LTarget: "foo",
  2259  						RTarget: "foo",
  2260  						Operand: "foo",
  2261  						str:     "foo",
  2262  					},
  2263  					{
  2264  						LTarget: "baz",
  2265  						RTarget: "baz",
  2266  						Operand: "baz",
  2267  						str:     "baz",
  2268  					},
  2269  				},
  2270  			},
  2271  			Expected: &TaskDiff{
  2272  				Type: DiffTypeEdited,
  2273  				Objects: []*ObjectDiff{
  2274  					{
  2275  						Type: DiffTypeAdded,
  2276  						Name: "Constraint",
  2277  						Fields: []*FieldDiff{
  2278  							{
  2279  								Type: DiffTypeAdded,
  2280  								Name: "LTarget",
  2281  								Old:  "",
  2282  								New:  "baz",
  2283  							},
  2284  							{
  2285  								Type: DiffTypeAdded,
  2286  								Name: "Operand",
  2287  								Old:  "",
  2288  								New:  "baz",
  2289  							},
  2290  							{
  2291  								Type: DiffTypeAdded,
  2292  								Name: "RTarget",
  2293  								Old:  "",
  2294  								New:  "baz",
  2295  							},
  2296  						},
  2297  					},
  2298  					{
  2299  						Type: DiffTypeDeleted,
  2300  						Name: "Constraint",
  2301  						Fields: []*FieldDiff{
  2302  							{
  2303  								Type: DiffTypeDeleted,
  2304  								Name: "LTarget",
  2305  								Old:  "bar",
  2306  								New:  "",
  2307  							},
  2308  							{
  2309  								Type: DiffTypeDeleted,
  2310  								Name: "Operand",
  2311  								Old:  "bar",
  2312  								New:  "",
  2313  							},
  2314  							{
  2315  								Type: DiffTypeDeleted,
  2316  								Name: "RTarget",
  2317  								Old:  "bar",
  2318  								New:  "",
  2319  							},
  2320  						},
  2321  					},
  2322  				},
  2323  			},
  2324  		},
  2325  		{
  2326  			Name: "LogConfig added",
  2327  			Old:  &Task{},
  2328  			New: &Task{
  2329  				LogConfig: &LogConfig{
  2330  					MaxFiles:      1,
  2331  					MaxFileSizeMB: 10,
  2332  				},
  2333  			},
  2334  			Expected: &TaskDiff{
  2335  				Type: DiffTypeEdited,
  2336  				Objects: []*ObjectDiff{
  2337  					{
  2338  						Type: DiffTypeAdded,
  2339  						Name: "LogConfig",
  2340  						Fields: []*FieldDiff{
  2341  							{
  2342  								Type: DiffTypeAdded,
  2343  								Name: "MaxFileSizeMB",
  2344  								Old:  "",
  2345  								New:  "10",
  2346  							},
  2347  							{
  2348  								Type: DiffTypeAdded,
  2349  								Name: "MaxFiles",
  2350  								Old:  "",
  2351  								New:  "1",
  2352  							},
  2353  						},
  2354  					},
  2355  				},
  2356  			},
  2357  		},
  2358  		{
  2359  			Name: "LogConfig deleted",
  2360  			Old: &Task{
  2361  				LogConfig: &LogConfig{
  2362  					MaxFiles:      1,
  2363  					MaxFileSizeMB: 10,
  2364  				},
  2365  			},
  2366  			New: &Task{},
  2367  			Expected: &TaskDiff{
  2368  				Type: DiffTypeEdited,
  2369  				Objects: []*ObjectDiff{
  2370  					{
  2371  						Type: DiffTypeDeleted,
  2372  						Name: "LogConfig",
  2373  						Fields: []*FieldDiff{
  2374  							{
  2375  								Type: DiffTypeDeleted,
  2376  								Name: "MaxFileSizeMB",
  2377  								Old:  "10",
  2378  								New:  "",
  2379  							},
  2380  							{
  2381  								Type: DiffTypeDeleted,
  2382  								Name: "MaxFiles",
  2383  								Old:  "1",
  2384  								New:  "",
  2385  							},
  2386  						},
  2387  					},
  2388  				},
  2389  			},
  2390  		},
  2391  		{
  2392  			Name: "LogConfig edited",
  2393  			Old: &Task{
  2394  				LogConfig: &LogConfig{
  2395  					MaxFiles:      1,
  2396  					MaxFileSizeMB: 10,
  2397  				},
  2398  			},
  2399  			New: &Task{
  2400  				LogConfig: &LogConfig{
  2401  					MaxFiles:      2,
  2402  					MaxFileSizeMB: 20,
  2403  				},
  2404  			},
  2405  			Expected: &TaskDiff{
  2406  				Type: DiffTypeEdited,
  2407  				Objects: []*ObjectDiff{
  2408  					{
  2409  						Type: DiffTypeEdited,
  2410  						Name: "LogConfig",
  2411  						Fields: []*FieldDiff{
  2412  							{
  2413  								Type: DiffTypeEdited,
  2414  								Name: "MaxFileSizeMB",
  2415  								Old:  "10",
  2416  								New:  "20",
  2417  							},
  2418  							{
  2419  								Type: DiffTypeEdited,
  2420  								Name: "MaxFiles",
  2421  								Old:  "1",
  2422  								New:  "2",
  2423  							},
  2424  						},
  2425  					},
  2426  				},
  2427  			},
  2428  		},
  2429  		{
  2430  			Name:       "LogConfig edited with context",
  2431  			Contextual: true,
  2432  			Old: &Task{
  2433  				LogConfig: &LogConfig{
  2434  					MaxFiles:      1,
  2435  					MaxFileSizeMB: 10,
  2436  				},
  2437  			},
  2438  			New: &Task{
  2439  				LogConfig: &LogConfig{
  2440  					MaxFiles:      1,
  2441  					MaxFileSizeMB: 20,
  2442  				},
  2443  			},
  2444  			Expected: &TaskDiff{
  2445  				Type: DiffTypeEdited,
  2446  				Objects: []*ObjectDiff{
  2447  					{
  2448  						Type: DiffTypeEdited,
  2449  						Name: "LogConfig",
  2450  						Fields: []*FieldDiff{
  2451  							{
  2452  								Type: DiffTypeEdited,
  2453  								Name: "MaxFileSizeMB",
  2454  								Old:  "10",
  2455  								New:  "20",
  2456  							},
  2457  							{
  2458  								Type: DiffTypeNone,
  2459  								Name: "MaxFiles",
  2460  								Old:  "1",
  2461  								New:  "1",
  2462  							},
  2463  						},
  2464  					},
  2465  				},
  2466  			},
  2467  		},
  2468  		{
  2469  			Name: "Artifacts edited",
  2470  			Old: &Task{
  2471  				Artifacts: []*TaskArtifact{
  2472  					{
  2473  						GetterSource: "foo",
  2474  						GetterOptions: map[string]string{
  2475  							"foo": "bar",
  2476  						},
  2477  						RelativeDest: "foo",
  2478  					},
  2479  					{
  2480  						GetterSource: "bar",
  2481  						GetterOptions: map[string]string{
  2482  							"bar": "baz",
  2483  						},
  2484  						GetterMode:   "dir",
  2485  						RelativeDest: "bar",
  2486  					},
  2487  				},
  2488  			},
  2489  			New: &Task{
  2490  				Artifacts: []*TaskArtifact{
  2491  					{
  2492  						GetterSource: "foo",
  2493  						GetterOptions: map[string]string{
  2494  							"foo": "bar",
  2495  						},
  2496  						RelativeDest: "foo",
  2497  					},
  2498  					{
  2499  						GetterSource: "bam",
  2500  						GetterOptions: map[string]string{
  2501  							"bam": "baz",
  2502  						},
  2503  						GetterMode:   "file",
  2504  						RelativeDest: "bam",
  2505  					},
  2506  				},
  2507  			},
  2508  			Expected: &TaskDiff{
  2509  				Type: DiffTypeEdited,
  2510  				Objects: []*ObjectDiff{
  2511  					{
  2512  						Type: DiffTypeAdded,
  2513  						Name: "Artifact",
  2514  						Fields: []*FieldDiff{
  2515  							{
  2516  								Type: DiffTypeAdded,
  2517  								Name: "GetterMode",
  2518  								Old:  "",
  2519  								New:  "file",
  2520  							},
  2521  							{
  2522  								Type: DiffTypeAdded,
  2523  								Name: "GetterOptions[bam]",
  2524  								Old:  "",
  2525  								New:  "baz",
  2526  							},
  2527  							{
  2528  								Type: DiffTypeAdded,
  2529  								Name: "GetterSource",
  2530  								Old:  "",
  2531  								New:  "bam",
  2532  							},
  2533  							{
  2534  								Type: DiffTypeAdded,
  2535  								Name: "RelativeDest",
  2536  								Old:  "",
  2537  								New:  "bam",
  2538  							},
  2539  						},
  2540  					},
  2541  					{
  2542  						Type: DiffTypeDeleted,
  2543  						Name: "Artifact",
  2544  						Fields: []*FieldDiff{
  2545  							{
  2546  								Type: DiffTypeDeleted,
  2547  								Name: "GetterMode",
  2548  								Old:  "dir",
  2549  								New:  "",
  2550  							},
  2551  							{
  2552  								Type: DiffTypeDeleted,
  2553  								Name: "GetterOptions[bar]",
  2554  								Old:  "baz",
  2555  								New:  "",
  2556  							},
  2557  							{
  2558  								Type: DiffTypeDeleted,
  2559  								Name: "GetterSource",
  2560  								Old:  "bar",
  2561  								New:  "",
  2562  							},
  2563  							{
  2564  								Type: DiffTypeDeleted,
  2565  								Name: "RelativeDest",
  2566  								Old:  "bar",
  2567  								New:  "",
  2568  							},
  2569  						},
  2570  					},
  2571  				},
  2572  			},
  2573  		},
  2574  		{
  2575  			Name: "Resources edited (no networks)",
  2576  			Old: &Task{
  2577  				Resources: &Resources{
  2578  					CPU:      100,
  2579  					MemoryMB: 100,
  2580  					DiskMB:   100,
  2581  					IOPS:     100,
  2582  				},
  2583  			},
  2584  			New: &Task{
  2585  				Resources: &Resources{
  2586  					CPU:      200,
  2587  					MemoryMB: 200,
  2588  					DiskMB:   200,
  2589  					IOPS:     200,
  2590  				},
  2591  			},
  2592  			Expected: &TaskDiff{
  2593  				Type: DiffTypeEdited,
  2594  				Objects: []*ObjectDiff{
  2595  					{
  2596  						Type: DiffTypeEdited,
  2597  						Name: "Resources",
  2598  						Fields: []*FieldDiff{
  2599  							{
  2600  								Type: DiffTypeEdited,
  2601  								Name: "CPU",
  2602  								Old:  "100",
  2603  								New:  "200",
  2604  							},
  2605  							{
  2606  								Type: DiffTypeEdited,
  2607  								Name: "DiskMB",
  2608  								Old:  "100",
  2609  								New:  "200",
  2610  							},
  2611  							{
  2612  								Type: DiffTypeEdited,
  2613  								Name: "IOPS",
  2614  								Old:  "100",
  2615  								New:  "200",
  2616  							},
  2617  							{
  2618  								Type: DiffTypeEdited,
  2619  								Name: "MemoryMB",
  2620  								Old:  "100",
  2621  								New:  "200",
  2622  							},
  2623  						},
  2624  					},
  2625  				},
  2626  			},
  2627  		},
  2628  		{
  2629  			Name:       "Resources edited (no networks) with context",
  2630  			Contextual: true,
  2631  			Old: &Task{
  2632  				Resources: &Resources{
  2633  					CPU:      100,
  2634  					MemoryMB: 100,
  2635  					DiskMB:   100,
  2636  					IOPS:     100,
  2637  				},
  2638  			},
  2639  			New: &Task{
  2640  				Resources: &Resources{
  2641  					CPU:      200,
  2642  					MemoryMB: 100,
  2643  					DiskMB:   200,
  2644  					IOPS:     100,
  2645  				},
  2646  			},
  2647  			Expected: &TaskDiff{
  2648  				Type: DiffTypeEdited,
  2649  				Objects: []*ObjectDiff{
  2650  					{
  2651  						Type: DiffTypeEdited,
  2652  						Name: "Resources",
  2653  						Fields: []*FieldDiff{
  2654  							{
  2655  								Type: DiffTypeEdited,
  2656  								Name: "CPU",
  2657  								Old:  "100",
  2658  								New:  "200",
  2659  							},
  2660  							{
  2661  								Type: DiffTypeEdited,
  2662  								Name: "DiskMB",
  2663  								Old:  "100",
  2664  								New:  "200",
  2665  							},
  2666  							{
  2667  								Type: DiffTypeNone,
  2668  								Name: "IOPS",
  2669  								Old:  "100",
  2670  								New:  "100",
  2671  							},
  2672  							{
  2673  								Type: DiffTypeNone,
  2674  								Name: "MemoryMB",
  2675  								Old:  "100",
  2676  								New:  "100",
  2677  							},
  2678  						},
  2679  					},
  2680  				},
  2681  			},
  2682  		},
  2683  		{
  2684  			Name: "Network Resources edited",
  2685  			Old: &Task{
  2686  				Resources: &Resources{
  2687  					Networks: []*NetworkResource{
  2688  						{
  2689  							Device: "foo",
  2690  							CIDR:   "foo",
  2691  							IP:     "foo",
  2692  							MBits:  100,
  2693  							ReservedPorts: []Port{
  2694  								{
  2695  									Label: "foo",
  2696  									Value: 80,
  2697  								},
  2698  							},
  2699  							DynamicPorts: []Port{
  2700  								{
  2701  									Label: "bar",
  2702  								},
  2703  							},
  2704  						},
  2705  					},
  2706  				},
  2707  			},
  2708  			New: &Task{
  2709  				Resources: &Resources{
  2710  					Networks: []*NetworkResource{
  2711  						{
  2712  							Device: "bar",
  2713  							CIDR:   "bar",
  2714  							IP:     "bar",
  2715  							MBits:  200,
  2716  							ReservedPorts: []Port{
  2717  								{
  2718  									Label: "foo",
  2719  									Value: 81,
  2720  								},
  2721  							},
  2722  							DynamicPorts: []Port{
  2723  								{
  2724  									Label: "baz",
  2725  								},
  2726  							},
  2727  						},
  2728  					},
  2729  				},
  2730  			},
  2731  			Expected: &TaskDiff{
  2732  				Type: DiffTypeEdited,
  2733  				Objects: []*ObjectDiff{
  2734  					{
  2735  						Type: DiffTypeEdited,
  2736  						Name: "Resources",
  2737  						Objects: []*ObjectDiff{
  2738  							{
  2739  								Type: DiffTypeAdded,
  2740  								Name: "Network",
  2741  								Fields: []*FieldDiff{
  2742  									{
  2743  										Type: DiffTypeAdded,
  2744  										Name: "MBits",
  2745  										Old:  "",
  2746  										New:  "200",
  2747  									},
  2748  								},
  2749  								Objects: []*ObjectDiff{
  2750  									{
  2751  										Type: DiffTypeAdded,
  2752  										Name: "Static Port",
  2753  										Fields: []*FieldDiff{
  2754  											{
  2755  												Type: DiffTypeAdded,
  2756  												Name: "Label",
  2757  												Old:  "",
  2758  												New:  "foo",
  2759  											},
  2760  											{
  2761  												Type: DiffTypeAdded,
  2762  												Name: "Value",
  2763  												Old:  "",
  2764  												New:  "81",
  2765  											},
  2766  										},
  2767  									},
  2768  									{
  2769  										Type: DiffTypeAdded,
  2770  										Name: "Dynamic Port",
  2771  										Fields: []*FieldDiff{
  2772  											{
  2773  												Type: DiffTypeAdded,
  2774  												Name: "Label",
  2775  												Old:  "",
  2776  												New:  "baz",
  2777  											},
  2778  										},
  2779  									},
  2780  								},
  2781  							},
  2782  							{
  2783  								Type: DiffTypeDeleted,
  2784  								Name: "Network",
  2785  								Fields: []*FieldDiff{
  2786  									{
  2787  										Type: DiffTypeDeleted,
  2788  										Name: "MBits",
  2789  										Old:  "100",
  2790  										New:  "",
  2791  									},
  2792  								},
  2793  								Objects: []*ObjectDiff{
  2794  									{
  2795  										Type: DiffTypeDeleted,
  2796  										Name: "Static Port",
  2797  										Fields: []*FieldDiff{
  2798  											{
  2799  												Type: DiffTypeDeleted,
  2800  												Name: "Label",
  2801  												Old:  "foo",
  2802  												New:  "",
  2803  											},
  2804  											{
  2805  												Type: DiffTypeDeleted,
  2806  												Name: "Value",
  2807  												Old:  "80",
  2808  												New:  "",
  2809  											},
  2810  										},
  2811  									},
  2812  									{
  2813  										Type: DiffTypeDeleted,
  2814  										Name: "Dynamic Port",
  2815  										Fields: []*FieldDiff{
  2816  											{
  2817  												Type: DiffTypeDeleted,
  2818  												Name: "Label",
  2819  												Old:  "bar",
  2820  												New:  "",
  2821  											},
  2822  										},
  2823  									},
  2824  								},
  2825  							},
  2826  						},
  2827  					},
  2828  				},
  2829  			},
  2830  		},
  2831  		{
  2832  			Name: "Config same",
  2833  			Old: &Task{
  2834  				Config: map[string]interface{}{
  2835  					"foo": 1,
  2836  					"bar": "bar",
  2837  					"bam": []string{"a", "b"},
  2838  					"baz": map[string]int{
  2839  						"a": 1,
  2840  						"b": 2,
  2841  					},
  2842  					"boom": &Port{
  2843  						Label: "boom_port",
  2844  					},
  2845  				},
  2846  			},
  2847  			New: &Task{
  2848  				Config: map[string]interface{}{
  2849  					"foo": 1,
  2850  					"bar": "bar",
  2851  					"bam": []string{"a", "b"},
  2852  					"baz": map[string]int{
  2853  						"a": 1,
  2854  						"b": 2,
  2855  					},
  2856  					"boom": &Port{
  2857  						Label: "boom_port",
  2858  					},
  2859  				},
  2860  			},
  2861  			Expected: &TaskDiff{
  2862  				Type: DiffTypeNone,
  2863  			},
  2864  		},
  2865  		{
  2866  			Name: "Config edited",
  2867  			Old: &Task{
  2868  				Config: map[string]interface{}{
  2869  					"foo": 1,
  2870  					"bar": "baz",
  2871  					"bam": []string{"a", "b"},
  2872  					"baz": map[string]int{
  2873  						"a": 1,
  2874  						"b": 2,
  2875  					},
  2876  					"boom": &Port{
  2877  						Label: "boom_port",
  2878  					},
  2879  				},
  2880  			},
  2881  			New: &Task{
  2882  				Config: map[string]interface{}{
  2883  					"foo": 2,
  2884  					"bar": "baz",
  2885  					"bam": []string{"a", "c", "d"},
  2886  					"baz": map[string]int{
  2887  						"b": 3,
  2888  						"c": 4,
  2889  					},
  2890  					"boom": &Port{
  2891  						Label: "boom_port2",
  2892  					},
  2893  				},
  2894  			},
  2895  			Expected: &TaskDiff{
  2896  				Type: DiffTypeEdited,
  2897  				Objects: []*ObjectDiff{
  2898  					{
  2899  						Type: DiffTypeEdited,
  2900  						Name: "Config",
  2901  						Fields: []*FieldDiff{
  2902  							{
  2903  								Type: DiffTypeEdited,
  2904  								Name: "bam[1]",
  2905  								Old:  "b",
  2906  								New:  "c",
  2907  							},
  2908  							{
  2909  								Type: DiffTypeAdded,
  2910  								Name: "bam[2]",
  2911  								Old:  "",
  2912  								New:  "d",
  2913  							},
  2914  							{
  2915  								Type: DiffTypeDeleted,
  2916  								Name: "baz[a]",
  2917  								Old:  "1",
  2918  								New:  "",
  2919  							},
  2920  							{
  2921  								Type: DiffTypeEdited,
  2922  								Name: "baz[b]",
  2923  								Old:  "2",
  2924  								New:  "3",
  2925  							},
  2926  							{
  2927  								Type: DiffTypeAdded,
  2928  								Name: "baz[c]",
  2929  								Old:  "",
  2930  								New:  "4",
  2931  							},
  2932  							{
  2933  								Type: DiffTypeEdited,
  2934  								Name: "boom.Label",
  2935  								Old:  "boom_port",
  2936  								New:  "boom_port2",
  2937  							},
  2938  							{
  2939  								Type: DiffTypeEdited,
  2940  								Name: "foo",
  2941  								Old:  "1",
  2942  								New:  "2",
  2943  							},
  2944  						},
  2945  					},
  2946  				},
  2947  			},
  2948  		},
  2949  		{
  2950  			Name:       "Config edited with context",
  2951  			Contextual: true,
  2952  			Old: &Task{
  2953  				Config: map[string]interface{}{
  2954  					"foo": 1,
  2955  					"bar": "baz",
  2956  					"bam": []string{"a", "b"},
  2957  					"baz": map[string]int{
  2958  						"a": 1,
  2959  						"b": 2,
  2960  					},
  2961  					"boom": &Port{
  2962  						Label: "boom_port",
  2963  					},
  2964  				},
  2965  			},
  2966  			New: &Task{
  2967  				Config: map[string]interface{}{
  2968  					"foo": 2,
  2969  					"bar": "baz",
  2970  					"bam": []string{"a", "c", "d"},
  2971  					"baz": map[string]int{
  2972  						"a": 1,
  2973  						"b": 2,
  2974  					},
  2975  					"boom": &Port{
  2976  						Label: "boom_port",
  2977  					},
  2978  				},
  2979  			},
  2980  			Expected: &TaskDiff{
  2981  				Type: DiffTypeEdited,
  2982  				Objects: []*ObjectDiff{
  2983  					{
  2984  						Type: DiffTypeEdited,
  2985  						Name: "Config",
  2986  						Fields: []*FieldDiff{
  2987  							{
  2988  								Type: DiffTypeNone,
  2989  								Name: "bam[0]",
  2990  								Old:  "a",
  2991  								New:  "a",
  2992  							},
  2993  							{
  2994  								Type: DiffTypeEdited,
  2995  								Name: "bam[1]",
  2996  								Old:  "b",
  2997  								New:  "c",
  2998  							},
  2999  							{
  3000  								Type: DiffTypeAdded,
  3001  								Name: "bam[2]",
  3002  								Old:  "",
  3003  								New:  "d",
  3004  							},
  3005  							{
  3006  								Type: DiffTypeNone,
  3007  								Name: "bar",
  3008  								Old:  "baz",
  3009  								New:  "baz",
  3010  							},
  3011  							{
  3012  								Type: DiffTypeNone,
  3013  								Name: "baz[a]",
  3014  								Old:  "1",
  3015  								New:  "1",
  3016  							},
  3017  							{
  3018  								Type: DiffTypeNone,
  3019  								Name: "baz[b]",
  3020  								Old:  "2",
  3021  								New:  "2",
  3022  							},
  3023  							{
  3024  								Type: DiffTypeNone,
  3025  								Name: "boom.Label",
  3026  								Old:  "boom_port",
  3027  								New:  "boom_port",
  3028  							},
  3029  							{
  3030  								Type: DiffTypeNone,
  3031  								Name: "boom.Value",
  3032  								Old:  "0",
  3033  								New:  "0",
  3034  							},
  3035  							{
  3036  								Type: DiffTypeEdited,
  3037  								Name: "foo",
  3038  								Old:  "1",
  3039  								New:  "2",
  3040  							},
  3041  						},
  3042  					},
  3043  				},
  3044  			},
  3045  		},
  3046  		{
  3047  			Name: "Services edited (no checks)",
  3048  			Old: &Task{
  3049  				Services: []*Service{
  3050  					{
  3051  						Name:      "foo",
  3052  						PortLabel: "foo",
  3053  					},
  3054  					{
  3055  						Name:      "bar",
  3056  						PortLabel: "bar",
  3057  					},
  3058  					{
  3059  						Name:      "baz",
  3060  						PortLabel: "baz",
  3061  					},
  3062  				},
  3063  			},
  3064  			New: &Task{
  3065  				Services: []*Service{
  3066  					{
  3067  						Name:      "bar",
  3068  						PortLabel: "bar",
  3069  					},
  3070  					{
  3071  						Name:      "baz",
  3072  						PortLabel: "baz2",
  3073  					},
  3074  					{
  3075  						Name:      "bam",
  3076  						PortLabel: "bam",
  3077  					},
  3078  				},
  3079  			},
  3080  			Expected: &TaskDiff{
  3081  				Type: DiffTypeEdited,
  3082  				Objects: []*ObjectDiff{
  3083  					{
  3084  						Type: DiffTypeEdited,
  3085  						Name: "Service",
  3086  						Fields: []*FieldDiff{
  3087  							{
  3088  								Type: DiffTypeEdited,
  3089  								Name: "PortLabel",
  3090  								Old:  "baz",
  3091  								New:  "baz2",
  3092  							},
  3093  						},
  3094  					},
  3095  					{
  3096  						Type: DiffTypeAdded,
  3097  						Name: "Service",
  3098  						Fields: []*FieldDiff{
  3099  							{
  3100  								Type: DiffTypeAdded,
  3101  								Name: "Name",
  3102  								Old:  "",
  3103  								New:  "bam",
  3104  							},
  3105  							{
  3106  								Type: DiffTypeAdded,
  3107  								Name: "PortLabel",
  3108  								Old:  "",
  3109  								New:  "bam",
  3110  							},
  3111  						},
  3112  					},
  3113  					{
  3114  						Type: DiffTypeDeleted,
  3115  						Name: "Service",
  3116  						Fields: []*FieldDiff{
  3117  							{
  3118  								Type: DiffTypeDeleted,
  3119  								Name: "Name",
  3120  								Old:  "foo",
  3121  								New:  "",
  3122  							},
  3123  							{
  3124  								Type: DiffTypeDeleted,
  3125  								Name: "PortLabel",
  3126  								Old:  "foo",
  3127  								New:  "",
  3128  							},
  3129  						},
  3130  					},
  3131  				},
  3132  			},
  3133  		},
  3134  		{
  3135  			Name:       "Services edited (no checks) with context",
  3136  			Contextual: true,
  3137  			Old: &Task{
  3138  				Services: []*Service{
  3139  					{
  3140  						Name:      "foo",
  3141  						PortLabel: "foo",
  3142  					},
  3143  				},
  3144  			},
  3145  			New: &Task{
  3146  				Services: []*Service{
  3147  					{
  3148  						Name:        "foo",
  3149  						PortLabel:   "bar",
  3150  						AddressMode: "driver",
  3151  					},
  3152  				},
  3153  			},
  3154  			Expected: &TaskDiff{
  3155  				Type: DiffTypeEdited,
  3156  				Objects: []*ObjectDiff{
  3157  					{
  3158  						Type: DiffTypeEdited,
  3159  						Name: "Service",
  3160  						Fields: []*FieldDiff{
  3161  							{
  3162  								Type: DiffTypeAdded,
  3163  								Name: "AddressMode",
  3164  								New:  "driver",
  3165  							},
  3166  							{
  3167  								Type: DiffTypeNone,
  3168  								Name: "Name",
  3169  								Old:  "foo",
  3170  								New:  "foo",
  3171  							},
  3172  							{
  3173  								Type: DiffTypeEdited,
  3174  								Name: "PortLabel",
  3175  								Old:  "foo",
  3176  								New:  "bar",
  3177  							},
  3178  						},
  3179  					},
  3180  				},
  3181  			},
  3182  		},
  3183  		{
  3184  			Name: "Service Checks edited",
  3185  			Old: &Task{
  3186  				Services: []*Service{
  3187  					{
  3188  						Name: "foo",
  3189  						Checks: []*ServiceCheck{
  3190  							{
  3191  								Name:     "foo",
  3192  								Type:     "http",
  3193  								Command:  "foo",
  3194  								Args:     []string{"foo"},
  3195  								Path:     "foo",
  3196  								Protocol: "http",
  3197  								Interval: 1 * time.Second,
  3198  								Timeout:  1 * time.Second,
  3199  								Header: map[string][]string{
  3200  									"Foo": {"bar"},
  3201  								},
  3202  							},
  3203  							{
  3204  								Name:     "bar",
  3205  								Type:     "http",
  3206  								Command:  "foo",
  3207  								Args:     []string{"foo"},
  3208  								Path:     "foo",
  3209  								Protocol: "http",
  3210  								Interval: 1 * time.Second,
  3211  								Timeout:  1 * time.Second,
  3212  							},
  3213  							{
  3214  								Name:     "baz",
  3215  								Type:     "http",
  3216  								Command:  "foo",
  3217  								Args:     []string{"foo"},
  3218  								Path:     "foo",
  3219  								Protocol: "http",
  3220  								Interval: 1 * time.Second,
  3221  								Timeout:  1 * time.Second,
  3222  							},
  3223  						},
  3224  					},
  3225  				},
  3226  			},
  3227  			New: &Task{
  3228  				Services: []*Service{
  3229  					{
  3230  						Name: "foo",
  3231  						Checks: []*ServiceCheck{
  3232  							{
  3233  								Name:     "bar",
  3234  								Type:     "http",
  3235  								Command:  "foo",
  3236  								Args:     []string{"foo"},
  3237  								Path:     "foo",
  3238  								Protocol: "http",
  3239  								Interval: 1 * time.Second,
  3240  								Timeout:  1 * time.Second,
  3241  							},
  3242  							{
  3243  								Name:     "baz",
  3244  								Type:     "tcp",
  3245  								Command:  "foo",
  3246  								Args:     []string{"foo"},
  3247  								Path:     "foo",
  3248  								Protocol: "http",
  3249  								Interval: 1 * time.Second,
  3250  								Timeout:  1 * time.Second,
  3251  								Header: map[string][]string{
  3252  									"Eggs": {"spam"},
  3253  								},
  3254  							},
  3255  							{
  3256  								Name:     "bam",
  3257  								Type:     "http",
  3258  								Command:  "foo",
  3259  								Args:     []string{"foo"},
  3260  								Path:     "foo",
  3261  								Protocol: "http",
  3262  								Interval: 1 * time.Second,
  3263  								Timeout:  1 * time.Second,
  3264  							},
  3265  						},
  3266  					},
  3267  				},
  3268  			},
  3269  			Expected: &TaskDiff{
  3270  				Type: DiffTypeEdited,
  3271  				Objects: []*ObjectDiff{
  3272  					{
  3273  						Type: DiffTypeEdited,
  3274  						Name: "Service",
  3275  						Objects: []*ObjectDiff{
  3276  							{
  3277  								Type: DiffTypeEdited,
  3278  								Name: "Check",
  3279  								Fields: []*FieldDiff{
  3280  									{
  3281  										Type: DiffTypeEdited,
  3282  										Name: "Type",
  3283  										Old:  "http",
  3284  										New:  "tcp",
  3285  									},
  3286  								},
  3287  								Objects: []*ObjectDiff{
  3288  									{
  3289  										Type: DiffTypeAdded,
  3290  										Name: "Header",
  3291  										Fields: []*FieldDiff{
  3292  											{
  3293  												Type: DiffTypeAdded,
  3294  												Name: "Eggs[0]",
  3295  												Old:  "",
  3296  												New:  "spam",
  3297  											},
  3298  										},
  3299  									},
  3300  								},
  3301  							},
  3302  							{
  3303  								Type: DiffTypeAdded,
  3304  								Name: "Check",
  3305  								Fields: []*FieldDiff{
  3306  									{
  3307  										Type: DiffTypeAdded,
  3308  										Name: "Command",
  3309  										Old:  "",
  3310  										New:  "foo",
  3311  									},
  3312  									{
  3313  										Type: DiffTypeAdded,
  3314  										Name: "Interval",
  3315  										Old:  "",
  3316  										New:  "1000000000",
  3317  									},
  3318  									{
  3319  										Type: DiffTypeAdded,
  3320  										Name: "Name",
  3321  										Old:  "",
  3322  										New:  "bam",
  3323  									},
  3324  									{
  3325  										Type: DiffTypeAdded,
  3326  										Name: "Path",
  3327  										Old:  "",
  3328  										New:  "foo",
  3329  									},
  3330  									{
  3331  										Type: DiffTypeAdded,
  3332  										Name: "Protocol",
  3333  										Old:  "",
  3334  										New:  "http",
  3335  									},
  3336  									{
  3337  										Type: DiffTypeAdded,
  3338  										Name: "TLSSkipVerify",
  3339  										Old:  "",
  3340  										New:  "false",
  3341  									},
  3342  									{
  3343  										Type: DiffTypeAdded,
  3344  										Name: "Timeout",
  3345  										Old:  "",
  3346  										New:  "1000000000",
  3347  									},
  3348  									{
  3349  										Type: DiffTypeAdded,
  3350  										Name: "Type",
  3351  										Old:  "",
  3352  										New:  "http",
  3353  									},
  3354  								},
  3355  							},
  3356  							{
  3357  								Type: DiffTypeDeleted,
  3358  								Name: "Check",
  3359  								Fields: []*FieldDiff{
  3360  									{
  3361  										Type: DiffTypeDeleted,
  3362  										Name: "Command",
  3363  										Old:  "foo",
  3364  										New:  "",
  3365  									},
  3366  									{
  3367  										Type: DiffTypeDeleted,
  3368  										Name: "Interval",
  3369  										Old:  "1000000000",
  3370  										New:  "",
  3371  									},
  3372  									{
  3373  										Type: DiffTypeDeleted,
  3374  										Name: "Name",
  3375  										Old:  "foo",
  3376  										New:  "",
  3377  									},
  3378  									{
  3379  										Type: DiffTypeDeleted,
  3380  										Name: "Path",
  3381  										Old:  "foo",
  3382  										New:  "",
  3383  									},
  3384  									{
  3385  										Type: DiffTypeDeleted,
  3386  										Name: "Protocol",
  3387  										Old:  "http",
  3388  										New:  "",
  3389  									},
  3390  									{
  3391  										Type: DiffTypeDeleted,
  3392  										Name: "TLSSkipVerify",
  3393  										Old:  "false",
  3394  										New:  "",
  3395  									},
  3396  									{
  3397  										Type: DiffTypeDeleted,
  3398  										Name: "Timeout",
  3399  										Old:  "1000000000",
  3400  										New:  "",
  3401  									},
  3402  									{
  3403  										Type: DiffTypeDeleted,
  3404  										Name: "Type",
  3405  										Old:  "http",
  3406  										New:  "",
  3407  									},
  3408  								},
  3409  								Objects: []*ObjectDiff{
  3410  									{
  3411  										Type: DiffTypeDeleted,
  3412  										Name: "Header",
  3413  										Fields: []*FieldDiff{
  3414  											{
  3415  												Type: DiffTypeDeleted,
  3416  												Name: "Foo[0]",
  3417  												Old:  "bar",
  3418  											},
  3419  										},
  3420  									},
  3421  								},
  3422  							},
  3423  						},
  3424  					},
  3425  				},
  3426  			},
  3427  		},
  3428  		{
  3429  			Name:       "Service Checks edited with context",
  3430  			Contextual: true,
  3431  			Old: &Task{
  3432  				Services: []*Service{
  3433  					{
  3434  						Name: "foo",
  3435  						Checks: []*ServiceCheck{
  3436  							{
  3437  								Name:          "foo",
  3438  								Type:          "http",
  3439  								Command:       "foo",
  3440  								Args:          []string{"foo"},
  3441  								Path:          "foo",
  3442  								Protocol:      "http",
  3443  								Interval:      1 * time.Second,
  3444  								Timeout:       1 * time.Second,
  3445  								InitialStatus: "critical",
  3446  								Header: map[string][]string{
  3447  									"Foo": {"bar"},
  3448  								},
  3449  							},
  3450  						},
  3451  					},
  3452  				},
  3453  			},
  3454  			New: &Task{
  3455  				Services: []*Service{
  3456  					{
  3457  						Name: "foo",
  3458  						Checks: []*ServiceCheck{
  3459  							{
  3460  								Name:          "foo",
  3461  								Type:          "tcp",
  3462  								Command:       "foo",
  3463  								Args:          []string{"foo"},
  3464  								Path:          "foo",
  3465  								Protocol:      "http",
  3466  								Interval:      1 * time.Second,
  3467  								Timeout:       1 * time.Second,
  3468  								InitialStatus: "passing",
  3469  								Method:        "POST",
  3470  								Header: map[string][]string{
  3471  									"Foo":  {"bar", "baz"},
  3472  									"Eggs": {"spam"},
  3473  								},
  3474  							},
  3475  						},
  3476  					},
  3477  				},
  3478  			},
  3479  			Expected: &TaskDiff{
  3480  				Type: DiffTypeEdited,
  3481  				Objects: []*ObjectDiff{
  3482  					{
  3483  						Type: DiffTypeEdited,
  3484  						Name: "Service",
  3485  						Fields: []*FieldDiff{
  3486  							{
  3487  								Type: DiffTypeNone,
  3488  								Name: "AddressMode",
  3489  								Old:  "",
  3490  								New:  "",
  3491  							},
  3492  							{
  3493  								Type: DiffTypeNone,
  3494  								Name: "Name",
  3495  								Old:  "foo",
  3496  								New:  "foo",
  3497  							},
  3498  							{
  3499  								Type: DiffTypeNone,
  3500  								Name: "PortLabel",
  3501  								Old:  "",
  3502  								New:  "",
  3503  							},
  3504  						},
  3505  						Objects: []*ObjectDiff{
  3506  							{
  3507  								Type: DiffTypeEdited,
  3508  								Name: "Check",
  3509  								Fields: []*FieldDiff{
  3510  									{
  3511  										Type: DiffTypeNone,
  3512  										Name: "AddressMode",
  3513  										Old:  "",
  3514  										New:  "",
  3515  									},
  3516  									{
  3517  										Type: DiffTypeNone,
  3518  										Name: "Command",
  3519  										Old:  "foo",
  3520  										New:  "foo",
  3521  									},
  3522  									{
  3523  										Type: DiffTypeEdited,
  3524  										Name: "InitialStatus",
  3525  										Old:  "critical",
  3526  										New:  "passing",
  3527  									},
  3528  									{
  3529  										Type: DiffTypeNone,
  3530  										Name: "Interval",
  3531  										Old:  "1000000000",
  3532  										New:  "1000000000",
  3533  									},
  3534  									{
  3535  										Type: DiffTypeAdded,
  3536  										Name: "Method",
  3537  										Old:  "",
  3538  										New:  "POST",
  3539  									},
  3540  									{
  3541  										Type: DiffTypeNone,
  3542  										Name: "Name",
  3543  										Old:  "foo",
  3544  										New:  "foo",
  3545  									},
  3546  									{
  3547  										Type: DiffTypeNone,
  3548  										Name: "Path",
  3549  										Old:  "foo",
  3550  										New:  "foo",
  3551  									},
  3552  									{
  3553  										Type: DiffTypeNone,
  3554  										Name: "PortLabel",
  3555  										Old:  "",
  3556  										New:  "",
  3557  									},
  3558  									{
  3559  										Type: DiffTypeNone,
  3560  										Name: "Protocol",
  3561  										Old:  "http",
  3562  										New:  "http",
  3563  									},
  3564  									{
  3565  										Type: DiffTypeNone,
  3566  										Name: "TLSSkipVerify",
  3567  										Old:  "false",
  3568  										New:  "false",
  3569  									},
  3570  									{
  3571  										Type: DiffTypeNone,
  3572  										Name: "Timeout",
  3573  										Old:  "1000000000",
  3574  										New:  "1000000000",
  3575  									},
  3576  									{
  3577  										Type: DiffTypeEdited,
  3578  										Name: "Type",
  3579  										Old:  "http",
  3580  										New:  "tcp",
  3581  									},
  3582  								},
  3583  								Objects: []*ObjectDiff{
  3584  									{
  3585  										Type: DiffTypeEdited,
  3586  										Name: "Header",
  3587  										Fields: []*FieldDiff{
  3588  											{
  3589  												Type: DiffTypeAdded,
  3590  												Name: "Eggs[0]",
  3591  												Old:  "",
  3592  												New:  "spam",
  3593  											},
  3594  											{
  3595  												Type: DiffTypeNone,
  3596  												Name: "Foo[0]",
  3597  												Old:  "bar",
  3598  												New:  "bar",
  3599  											},
  3600  											{
  3601  												Type: DiffTypeAdded,
  3602  												Name: "Foo[1]",
  3603  												Old:  "",
  3604  												New:  "baz",
  3605  											},
  3606  										},
  3607  									},
  3608  								},
  3609  							},
  3610  						},
  3611  					},
  3612  				},
  3613  			},
  3614  		},
  3615  		{
  3616  			Name: "CheckRestart edited",
  3617  			Old: &Task{
  3618  				Services: []*Service{
  3619  					{
  3620  						Name: "foo",
  3621  						Checks: []*ServiceCheck{
  3622  							{
  3623  								Name:     "foo",
  3624  								Type:     "http",
  3625  								Command:  "foo",
  3626  								Args:     []string{"foo"},
  3627  								Path:     "foo",
  3628  								Protocol: "http",
  3629  								Interval: 1 * time.Second,
  3630  								Timeout:  1 * time.Second,
  3631  							},
  3632  							{
  3633  								Name:     "bar",
  3634  								Type:     "http",
  3635  								Command:  "foo",
  3636  								Args:     []string{"foo"},
  3637  								Path:     "foo",
  3638  								Protocol: "http",
  3639  								Interval: 1 * time.Second,
  3640  								Timeout:  1 * time.Second,
  3641  								CheckRestart: &CheckRestart{
  3642  									Limit: 2,
  3643  									Grace: 2 * time.Second,
  3644  								},
  3645  							},
  3646  							{
  3647  								Name:     "baz",
  3648  								Type:     "http",
  3649  								Command:  "foo",
  3650  								Args:     []string{"foo"},
  3651  								Path:     "foo",
  3652  								Protocol: "http",
  3653  								Interval: 1 * time.Second,
  3654  								Timeout:  1 * time.Second,
  3655  								CheckRestart: &CheckRestart{
  3656  									Limit: 3,
  3657  									Grace: 3 * time.Second,
  3658  								},
  3659  							},
  3660  						},
  3661  					},
  3662  				},
  3663  			},
  3664  			New: &Task{
  3665  				Services: []*Service{
  3666  					{
  3667  						Name: "foo",
  3668  						Checks: []*ServiceCheck{
  3669  							{
  3670  								Name:     "foo",
  3671  								Type:     "http",
  3672  								Command:  "foo",
  3673  								Args:     []string{"foo"},
  3674  								Path:     "foo",
  3675  								Protocol: "http",
  3676  								Interval: 1 * time.Second,
  3677  								Timeout:  1 * time.Second,
  3678  								CheckRestart: &CheckRestart{
  3679  									Limit: 1,
  3680  									Grace: 1 * time.Second,
  3681  								},
  3682  							},
  3683  							{
  3684  								Name:     "bar",
  3685  								Type:     "http",
  3686  								Command:  "foo",
  3687  								Args:     []string{"foo"},
  3688  								Path:     "foo",
  3689  								Protocol: "http",
  3690  								Interval: 1 * time.Second,
  3691  								Timeout:  1 * time.Second,
  3692  							},
  3693  							{
  3694  								Name:     "baz",
  3695  								Type:     "http",
  3696  								Command:  "foo",
  3697  								Args:     []string{"foo"},
  3698  								Path:     "foo",
  3699  								Protocol: "http",
  3700  								Interval: 1 * time.Second,
  3701  								Timeout:  1 * time.Second,
  3702  								CheckRestart: &CheckRestart{
  3703  									Limit: 4,
  3704  									Grace: 4 * time.Second,
  3705  								},
  3706  							},
  3707  						},
  3708  					},
  3709  				},
  3710  			},
  3711  			Expected: &TaskDiff{
  3712  				Type: DiffTypeEdited,
  3713  				Objects: []*ObjectDiff{
  3714  					{
  3715  						Type: DiffTypeEdited,
  3716  						Name: "Service",
  3717  						Objects: []*ObjectDiff{
  3718  							{
  3719  								Type: DiffTypeEdited,
  3720  								Name: "Check",
  3721  								Objects: []*ObjectDiff{
  3722  									{
  3723  										Type: DiffTypeEdited,
  3724  										Name: "CheckRestart",
  3725  										Fields: []*FieldDiff{
  3726  											{
  3727  												Type: DiffTypeEdited,
  3728  												Name: "Grace",
  3729  												Old:  "3000000000",
  3730  												New:  "4000000000",
  3731  											},
  3732  											{
  3733  												Type: DiffTypeEdited,
  3734  												Name: "Limit",
  3735  												Old:  "3",
  3736  												New:  "4",
  3737  											},
  3738  										},
  3739  									},
  3740  								},
  3741  							},
  3742  							{
  3743  								Type: DiffTypeEdited,
  3744  								Name: "Check",
  3745  								Objects: []*ObjectDiff{
  3746  									{
  3747  										Type: DiffTypeAdded,
  3748  										Name: "CheckRestart",
  3749  										Fields: []*FieldDiff{
  3750  											{
  3751  												Type: DiffTypeAdded,
  3752  												Name: "Grace",
  3753  												New:  "1000000000",
  3754  											},
  3755  											{
  3756  												Type: DiffTypeAdded,
  3757  												Name: "IgnoreWarnings",
  3758  												New:  "false",
  3759  											},
  3760  											{
  3761  												Type: DiffTypeAdded,
  3762  												Name: "Limit",
  3763  												New:  "1",
  3764  											},
  3765  										},
  3766  									},
  3767  								},
  3768  							},
  3769  							{
  3770  								Type: DiffTypeEdited,
  3771  								Name: "Check",
  3772  								Objects: []*ObjectDiff{
  3773  									{
  3774  										Type: DiffTypeDeleted,
  3775  										Name: "CheckRestart",
  3776  										Fields: []*FieldDiff{
  3777  											{
  3778  												Type: DiffTypeDeleted,
  3779  												Name: "Grace",
  3780  												Old:  "2000000000",
  3781  											},
  3782  											{
  3783  												Type: DiffTypeDeleted,
  3784  												Name: "IgnoreWarnings",
  3785  												Old:  "false",
  3786  											},
  3787  											{
  3788  												Type: DiffTypeDeleted,
  3789  												Name: "Limit",
  3790  												Old:  "2",
  3791  											},
  3792  										},
  3793  									},
  3794  								},
  3795  							},
  3796  						},
  3797  					},
  3798  				},
  3799  			},
  3800  		},
  3801  		{
  3802  			Name: "Vault added",
  3803  			Old:  &Task{},
  3804  			New: &Task{
  3805  				Vault: &Vault{
  3806  					Policies:     []string{"foo", "bar"},
  3807  					Env:          true,
  3808  					ChangeMode:   "signal",
  3809  					ChangeSignal: "SIGUSR1",
  3810  				},
  3811  			},
  3812  			Expected: &TaskDiff{
  3813  				Type: DiffTypeEdited,
  3814  				Objects: []*ObjectDiff{
  3815  					{
  3816  						Type: DiffTypeAdded,
  3817  						Name: "Vault",
  3818  						Fields: []*FieldDiff{
  3819  							{
  3820  								Type: DiffTypeAdded,
  3821  								Name: "ChangeMode",
  3822  								Old:  "",
  3823  								New:  "signal",
  3824  							},
  3825  							{
  3826  								Type: DiffTypeAdded,
  3827  								Name: "ChangeSignal",
  3828  								Old:  "",
  3829  								New:  "SIGUSR1",
  3830  							},
  3831  							{
  3832  								Type: DiffTypeAdded,
  3833  								Name: "Env",
  3834  								Old:  "",
  3835  								New:  "true",
  3836  							},
  3837  						},
  3838  						Objects: []*ObjectDiff{
  3839  							{
  3840  								Type: DiffTypeAdded,
  3841  								Name: "Policies",
  3842  								Fields: []*FieldDiff{
  3843  									{
  3844  										Type: DiffTypeAdded,
  3845  										Name: "Policies",
  3846  										Old:  "",
  3847  										New:  "bar",
  3848  									},
  3849  									{
  3850  										Type: DiffTypeAdded,
  3851  										Name: "Policies",
  3852  										Old:  "",
  3853  										New:  "foo",
  3854  									},
  3855  								},
  3856  							},
  3857  						},
  3858  					},
  3859  				},
  3860  			},
  3861  		},
  3862  		{
  3863  			Name: "Vault deleted",
  3864  			Old: &Task{
  3865  				Vault: &Vault{
  3866  					Policies:     []string{"foo", "bar"},
  3867  					Env:          true,
  3868  					ChangeMode:   "signal",
  3869  					ChangeSignal: "SIGUSR1",
  3870  				},
  3871  			},
  3872  			New: &Task{},
  3873  			Expected: &TaskDiff{
  3874  				Type: DiffTypeEdited,
  3875  				Objects: []*ObjectDiff{
  3876  					{
  3877  						Type: DiffTypeDeleted,
  3878  						Name: "Vault",
  3879  						Fields: []*FieldDiff{
  3880  							{
  3881  								Type: DiffTypeDeleted,
  3882  								Name: "ChangeMode",
  3883  								Old:  "signal",
  3884  								New:  "",
  3885  							},
  3886  							{
  3887  								Type: DiffTypeDeleted,
  3888  								Name: "ChangeSignal",
  3889  								Old:  "SIGUSR1",
  3890  								New:  "",
  3891  							},
  3892  							{
  3893  								Type: DiffTypeDeleted,
  3894  								Name: "Env",
  3895  								Old:  "true",
  3896  								New:  "",
  3897  							},
  3898  						},
  3899  						Objects: []*ObjectDiff{
  3900  							{
  3901  								Type: DiffTypeDeleted,
  3902  								Name: "Policies",
  3903  								Fields: []*FieldDiff{
  3904  									{
  3905  										Type: DiffTypeDeleted,
  3906  										Name: "Policies",
  3907  										Old:  "bar",
  3908  										New:  "",
  3909  									},
  3910  									{
  3911  										Type: DiffTypeDeleted,
  3912  										Name: "Policies",
  3913  										Old:  "foo",
  3914  										New:  "",
  3915  									},
  3916  								},
  3917  							},
  3918  						},
  3919  					},
  3920  				},
  3921  			},
  3922  		},
  3923  		{
  3924  			Name: "Vault edited",
  3925  			Old: &Task{
  3926  				Vault: &Vault{
  3927  					Policies:     []string{"foo", "bar"},
  3928  					Env:          true,
  3929  					ChangeMode:   "signal",
  3930  					ChangeSignal: "SIGUSR1",
  3931  				},
  3932  			},
  3933  			New: &Task{
  3934  				Vault: &Vault{
  3935  					Policies:     []string{"bar", "baz"},
  3936  					Env:          false,
  3937  					ChangeMode:   "restart",
  3938  					ChangeSignal: "foo",
  3939  				},
  3940  			},
  3941  			Expected: &TaskDiff{
  3942  				Type: DiffTypeEdited,
  3943  				Objects: []*ObjectDiff{
  3944  					{
  3945  						Type: DiffTypeEdited,
  3946  						Name: "Vault",
  3947  						Fields: []*FieldDiff{
  3948  							{
  3949  								Type: DiffTypeEdited,
  3950  								Name: "ChangeMode",
  3951  								Old:  "signal",
  3952  								New:  "restart",
  3953  							},
  3954  							{
  3955  								Type: DiffTypeEdited,
  3956  								Name: "ChangeSignal",
  3957  								Old:  "SIGUSR1",
  3958  								New:  "foo",
  3959  							},
  3960  							{
  3961  								Type: DiffTypeEdited,
  3962  								Name: "Env",
  3963  								Old:  "true",
  3964  								New:  "false",
  3965  							},
  3966  						},
  3967  						Objects: []*ObjectDiff{
  3968  							{
  3969  								Type: DiffTypeEdited,
  3970  								Name: "Policies",
  3971  								Fields: []*FieldDiff{
  3972  									{
  3973  										Type: DiffTypeAdded,
  3974  										Name: "Policies",
  3975  										Old:  "",
  3976  										New:  "baz",
  3977  									},
  3978  									{
  3979  										Type: DiffTypeDeleted,
  3980  										Name: "Policies",
  3981  										Old:  "foo",
  3982  										New:  "",
  3983  									},
  3984  								},
  3985  							},
  3986  						},
  3987  					},
  3988  				},
  3989  			},
  3990  		},
  3991  		{
  3992  			Name:       "Vault edited with context",
  3993  			Contextual: true,
  3994  			Old: &Task{
  3995  				Vault: &Vault{
  3996  					Policies:     []string{"foo", "bar"},
  3997  					Env:          true,
  3998  					ChangeMode:   "signal",
  3999  					ChangeSignal: "SIGUSR1",
  4000  				},
  4001  			},
  4002  			New: &Task{
  4003  				Vault: &Vault{
  4004  					Policies:     []string{"bar", "baz"},
  4005  					Env:          true,
  4006  					ChangeMode:   "signal",
  4007  					ChangeSignal: "SIGUSR1",
  4008  				},
  4009  			},
  4010  			Expected: &TaskDiff{
  4011  				Type: DiffTypeEdited,
  4012  				Objects: []*ObjectDiff{
  4013  					{
  4014  						Type: DiffTypeEdited,
  4015  						Name: "Vault",
  4016  						Fields: []*FieldDiff{
  4017  							{
  4018  								Type: DiffTypeNone,
  4019  								Name: "ChangeMode",
  4020  								Old:  "signal",
  4021  								New:  "signal",
  4022  							},
  4023  							{
  4024  								Type: DiffTypeNone,
  4025  								Name: "ChangeSignal",
  4026  								Old:  "SIGUSR1",
  4027  								New:  "SIGUSR1",
  4028  							},
  4029  							{
  4030  								Type: DiffTypeNone,
  4031  								Name: "Env",
  4032  								Old:  "true",
  4033  								New:  "true",
  4034  							},
  4035  						},
  4036  						Objects: []*ObjectDiff{
  4037  							{
  4038  								Type: DiffTypeEdited,
  4039  								Name: "Policies",
  4040  								Fields: []*FieldDiff{
  4041  									{
  4042  										Type: DiffTypeAdded,
  4043  										Name: "Policies",
  4044  										Old:  "",
  4045  										New:  "baz",
  4046  									},
  4047  									{
  4048  										Type: DiffTypeNone,
  4049  										Name: "Policies",
  4050  										Old:  "bar",
  4051  										New:  "bar",
  4052  									},
  4053  									{
  4054  										Type: DiffTypeDeleted,
  4055  										Name: "Policies",
  4056  										Old:  "foo",
  4057  										New:  "",
  4058  									},
  4059  								},
  4060  							},
  4061  						},
  4062  					},
  4063  				},
  4064  			},
  4065  		},
  4066  		{
  4067  			Name: "Template edited",
  4068  			Old: &Task{
  4069  				Templates: []*Template{
  4070  					{
  4071  						SourcePath:   "foo",
  4072  						DestPath:     "bar",
  4073  						EmbeddedTmpl: "baz",
  4074  						ChangeMode:   "bam",
  4075  						ChangeSignal: "SIGHUP",
  4076  						Splay:        1,
  4077  						Perms:        "0644",
  4078  						VaultGrace:   3 * time.Second,
  4079  					},
  4080  					{
  4081  						SourcePath:   "foo2",
  4082  						DestPath:     "bar2",
  4083  						EmbeddedTmpl: "baz2",
  4084  						ChangeMode:   "bam2",
  4085  						ChangeSignal: "SIGHUP2",
  4086  						Splay:        2,
  4087  						Perms:        "0666",
  4088  						Envvars:      true,
  4089  						VaultGrace:   5 * time.Second,
  4090  					},
  4091  				},
  4092  			},
  4093  			New: &Task{
  4094  				Templates: []*Template{
  4095  					{
  4096  						SourcePath:   "foo",
  4097  						DestPath:     "bar",
  4098  						EmbeddedTmpl: "baz",
  4099  						ChangeMode:   "bam",
  4100  						ChangeSignal: "SIGHUP",
  4101  						Splay:        1,
  4102  						Perms:        "0644",
  4103  						VaultGrace:   3 * time.Second,
  4104  					},
  4105  					{
  4106  						SourcePath:   "foo3",
  4107  						DestPath:     "bar3",
  4108  						EmbeddedTmpl: "baz3",
  4109  						ChangeMode:   "bam3",
  4110  						ChangeSignal: "SIGHUP3",
  4111  						Splay:        3,
  4112  						Perms:        "0776",
  4113  						VaultGrace:   10 * time.Second,
  4114  					},
  4115  				},
  4116  			},
  4117  			Expected: &TaskDiff{
  4118  				Type: DiffTypeEdited,
  4119  				Objects: []*ObjectDiff{
  4120  					{
  4121  						Type: DiffTypeAdded,
  4122  						Name: "Template",
  4123  						Fields: []*FieldDiff{
  4124  							{
  4125  								Type: DiffTypeAdded,
  4126  								Name: "ChangeMode",
  4127  								Old:  "",
  4128  								New:  "bam3",
  4129  							},
  4130  							{
  4131  								Type: DiffTypeAdded,
  4132  								Name: "ChangeSignal",
  4133  								Old:  "",
  4134  								New:  "SIGHUP3",
  4135  							},
  4136  							{
  4137  								Type: DiffTypeAdded,
  4138  								Name: "DestPath",
  4139  								Old:  "",
  4140  								New:  "bar3",
  4141  							},
  4142  							{
  4143  								Type: DiffTypeAdded,
  4144  								Name: "EmbeddedTmpl",
  4145  								Old:  "",
  4146  								New:  "baz3",
  4147  							},
  4148  							{
  4149  								Type: DiffTypeAdded,
  4150  								Name: "Envvars",
  4151  								Old:  "",
  4152  								New:  "false",
  4153  							},
  4154  							{
  4155  								Type: DiffTypeAdded,
  4156  								Name: "Perms",
  4157  								Old:  "",
  4158  								New:  "0776",
  4159  							},
  4160  							{
  4161  								Type: DiffTypeAdded,
  4162  								Name: "SourcePath",
  4163  								Old:  "",
  4164  								New:  "foo3",
  4165  							},
  4166  							{
  4167  								Type: DiffTypeAdded,
  4168  								Name: "Splay",
  4169  								Old:  "",
  4170  								New:  "3",
  4171  							},
  4172  							{
  4173  								Type: DiffTypeAdded,
  4174  								Name: "VaultGrace",
  4175  								Old:  "",
  4176  								New:  "10000000000",
  4177  							},
  4178  						},
  4179  					},
  4180  					{
  4181  						Type: DiffTypeDeleted,
  4182  						Name: "Template",
  4183  						Fields: []*FieldDiff{
  4184  							{
  4185  								Type: DiffTypeDeleted,
  4186  								Name: "ChangeMode",
  4187  								Old:  "bam2",
  4188  								New:  "",
  4189  							},
  4190  							{
  4191  								Type: DiffTypeDeleted,
  4192  								Name: "ChangeSignal",
  4193  								Old:  "SIGHUP2",
  4194  								New:  "",
  4195  							},
  4196  							{
  4197  								Type: DiffTypeDeleted,
  4198  								Name: "DestPath",
  4199  								Old:  "bar2",
  4200  								New:  "",
  4201  							},
  4202  							{
  4203  								Type: DiffTypeDeleted,
  4204  								Name: "EmbeddedTmpl",
  4205  								Old:  "baz2",
  4206  								New:  "",
  4207  							},
  4208  							{
  4209  								Type: DiffTypeDeleted,
  4210  								Name: "Envvars",
  4211  								Old:  "true",
  4212  								New:  "",
  4213  							},
  4214  							{
  4215  								Type: DiffTypeDeleted,
  4216  								Name: "Perms",
  4217  								Old:  "0666",
  4218  								New:  "",
  4219  							},
  4220  							{
  4221  								Type: DiffTypeDeleted,
  4222  								Name: "SourcePath",
  4223  								Old:  "foo2",
  4224  								New:  "",
  4225  							},
  4226  							{
  4227  								Type: DiffTypeDeleted,
  4228  								Name: "Splay",
  4229  								Old:  "2",
  4230  								New:  "",
  4231  							},
  4232  							{
  4233  								Type: DiffTypeDeleted,
  4234  								Name: "VaultGrace",
  4235  								Old:  "5000000000",
  4236  								New:  "",
  4237  							},
  4238  						},
  4239  					},
  4240  				},
  4241  			},
  4242  		},
  4243  		{
  4244  			Name: "DispatchPayload added",
  4245  			Old:  &Task{},
  4246  			New: &Task{
  4247  				DispatchPayload: &DispatchPayloadConfig{
  4248  					File: "foo",
  4249  				},
  4250  			},
  4251  			Expected: &TaskDiff{
  4252  				Type: DiffTypeEdited,
  4253  				Objects: []*ObjectDiff{
  4254  					{
  4255  						Type: DiffTypeAdded,
  4256  						Name: "DispatchPayload",
  4257  						Fields: []*FieldDiff{
  4258  							{
  4259  								Type: DiffTypeAdded,
  4260  								Name: "File",
  4261  								Old:  "",
  4262  								New:  "foo",
  4263  							},
  4264  						},
  4265  					},
  4266  				},
  4267  			},
  4268  		},
  4269  		{
  4270  			Name: "DispatchPayload deleted",
  4271  			Old: &Task{
  4272  				DispatchPayload: &DispatchPayloadConfig{
  4273  					File: "foo",
  4274  				},
  4275  			},
  4276  			New: &Task{},
  4277  			Expected: &TaskDiff{
  4278  				Type: DiffTypeEdited,
  4279  				Objects: []*ObjectDiff{
  4280  					{
  4281  						Type: DiffTypeDeleted,
  4282  						Name: "DispatchPayload",
  4283  						Fields: []*FieldDiff{
  4284  							{
  4285  								Type: DiffTypeDeleted,
  4286  								Name: "File",
  4287  								Old:  "foo",
  4288  								New:  "",
  4289  							},
  4290  						},
  4291  					},
  4292  				},
  4293  			},
  4294  		},
  4295  		{
  4296  			Name: "Dispatch payload edited",
  4297  			Old: &Task{
  4298  				DispatchPayload: &DispatchPayloadConfig{
  4299  					File: "foo",
  4300  				},
  4301  			},
  4302  			New: &Task{
  4303  				DispatchPayload: &DispatchPayloadConfig{
  4304  					File: "bar",
  4305  				},
  4306  			},
  4307  			Expected: &TaskDiff{
  4308  				Type: DiffTypeEdited,
  4309  				Objects: []*ObjectDiff{
  4310  					{
  4311  						Type: DiffTypeEdited,
  4312  						Name: "DispatchPayload",
  4313  						Fields: []*FieldDiff{
  4314  							{
  4315  								Type: DiffTypeEdited,
  4316  								Name: "File",
  4317  								Old:  "foo",
  4318  								New:  "bar",
  4319  							},
  4320  						},
  4321  					},
  4322  				},
  4323  			},
  4324  		},
  4325  		{
  4326  			// Place holder for if more fields are added
  4327  			Name:       "DispatchPayload edited with context",
  4328  			Contextual: true,
  4329  			Old: &Task{
  4330  				DispatchPayload: &DispatchPayloadConfig{
  4331  					File: "foo",
  4332  				},
  4333  			},
  4334  			New: &Task{
  4335  				DispatchPayload: &DispatchPayloadConfig{
  4336  					File: "bar",
  4337  				},
  4338  			},
  4339  			Expected: &TaskDiff{
  4340  				Type: DiffTypeEdited,
  4341  				Objects: []*ObjectDiff{
  4342  					{
  4343  						Type: DiffTypeEdited,
  4344  						Name: "DispatchPayload",
  4345  						Fields: []*FieldDiff{
  4346  							{
  4347  								Type: DiffTypeEdited,
  4348  								Name: "File",
  4349  								Old:  "foo",
  4350  								New:  "bar",
  4351  							},
  4352  						},
  4353  					},
  4354  				},
  4355  			},
  4356  		},
  4357  	}
  4358  
  4359  	for i, c := range cases {
  4360  		t.Run(c.Name, func(t *testing.T) {
  4361  			actual, err := c.Old.Diff(c.New, c.Contextual)
  4362  			if c.Error && err == nil {
  4363  				t.Fatalf("case %d: expected errored", i+1)
  4364  			} else if err != nil {
  4365  				if !c.Error {
  4366  					t.Fatalf("case %d: errored %#v", i+1, err)
  4367  				} else {
  4368  					return
  4369  				}
  4370  			}
  4371  
  4372  			if !reflect.DeepEqual(actual, c.Expected) {
  4373  				t.Errorf("case %d: got:\n%#v\n want:\n%#v\n",
  4374  					i+1, actual, c.Expected)
  4375  			}
  4376  		})
  4377  	}
  4378  }