deeplink_test.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package lunatask_test
  6
  7import (
  8	"errors"
  9	"testing"
 10
 11	lunatask "git.secluded.site/go-lunatask"
 12)
 13
 14func TestParseDeepLink(t *testing.T) {
 15	t.Parallel()
 16
 17	validUUID := "12345678-1234-1234-1234-123456789012"
 18	validUUID2 := "abcdef12-3456-7890-abcd-ef1234567890"
 19
 20	tests := []struct {
 21		name         string
 22		input        string
 23		wantResource lunatask.Resource
 24		wantUUID     string
 25		wantErr      error
 26	}{
 27		// Valid deep links
 28		{"task_link", "lunatask://tasks/" + validUUID, lunatask.ResourceTask, validUUID, nil},
 29		{"area_link", "lunatask://areas/" + validUUID, lunatask.ResourceArea, validUUID, nil},
 30		{"goal_link", "lunatask://goals/" + validUUID, lunatask.ResourceGoal, validUUID, nil},
 31		{"note_link", "lunatask://notes/" + validUUID, lunatask.ResourceNote, validUUID, nil},
 32		{"person_link", "lunatask://people/" + validUUID, lunatask.ResourcePerson, validUUID, nil},
 33		{"notebook_link", "lunatask://notebooks/" + validUUID, lunatask.ResourceNotebook, validUUID, nil},
 34
 35		// Plain UUIDs
 36		{"plain_uuid", validUUID2, "", validUUID2, nil},
 37		{"uuid_only", validUUID, "", validUUID, nil},
 38
 39		// Invalid inputs
 40		{"empty", "", "", "", lunatask.ErrInvalidDeepLink},
 41		{"invalid_resource", "lunatask://invalid/" + validUUID, "", "", lunatask.ErrInvalidResource},
 42		{"missing_uuid", "lunatask://tasks/", "", "", lunatask.ErrInvalidDeepLink},
 43		{"missing_resource", "lunatask:///" + validUUID, "", "", lunatask.ErrInvalidDeepLink},
 44		{"malformed", "lunatask://tasks", "", "", lunatask.ErrInvalidDeepLink},
 45		{"invalid_uuid_in_link", "lunatask://tasks/not-a-uuid", "", "", lunatask.ErrInvalidUUID},
 46		{"invalid_plain_uuid", "not-a-valid-uuid", "", "", lunatask.ErrInvalidUUID},
 47	}
 48
 49	for _, testCase := range tests {
 50		t.Run(testCase.name, func(t *testing.T) {
 51			t.Parallel()
 52
 53			resource, uuid, err := lunatask.ParseDeepLink(testCase.input)
 54
 55			if testCase.wantErr != nil {
 56				if !errors.Is(err, testCase.wantErr) {
 57					t.Errorf("ParseDeepLink(%q) error = %v, want %v", testCase.input, err, testCase.wantErr)
 58				}
 59
 60				return
 61			}
 62
 63			if err != nil {
 64				t.Errorf("ParseDeepLink(%q) unexpected error = %v", testCase.input, err)
 65
 66				return
 67			}
 68
 69			if resource != testCase.wantResource {
 70				t.Errorf("ParseDeepLink(%q) resource = %q, want %q", testCase.input, resource, testCase.wantResource)
 71			}
 72
 73			if uuid != testCase.wantUUID {
 74				t.Errorf("ParseDeepLink(%q) uuid = %q, want %q", testCase.input, uuid, testCase.wantUUID)
 75			}
 76		})
 77	}
 78}
 79
 80func TestBuildDeepLink(t *testing.T) {
 81	t.Parallel()
 82
 83	validUUID := "12345678-1234-1234-1234-123456789012"
 84
 85	tests := []struct {
 86		name     string
 87		resource lunatask.Resource
 88		id       string
 89		want     string
 90		wantErr  error
 91	}{
 92		// Valid builds
 93		{"task", lunatask.ResourceTask, validUUID, "lunatask://tasks/" + validUUID, nil},
 94		{"area", lunatask.ResourceArea, validUUID, "lunatask://areas/" + validUUID, nil},
 95		{"goal", lunatask.ResourceGoal, validUUID, "lunatask://goals/" + validUUID, nil},
 96		{"note", lunatask.ResourceNote, validUUID, "lunatask://notes/" + validUUID, nil},
 97		{"person", lunatask.ResourcePerson, validUUID, "lunatask://people/" + validUUID, nil},
 98		{"notebook", lunatask.ResourceNotebook, validUUID, "lunatask://notebooks/" + validUUID, nil},
 99
100		// Invalid inputs
101		{"empty_id", lunatask.ResourceTask, "", "", lunatask.ErrInvalidUUID},
102		{"invalid_resource", lunatask.Resource("invalid"), validUUID, "", lunatask.ErrInvalidResource},
103		{"invalid_uuid", lunatask.ResourceTask, "not-a-uuid", "", lunatask.ErrInvalidUUID},
104	}
105
106	for _, testCase := range tests {
107		t.Run(testCase.name, func(t *testing.T) {
108			t.Parallel()
109
110			got, err := lunatask.BuildDeepLink(testCase.resource, testCase.id)
111
112			if testCase.wantErr != nil {
113				if !errors.Is(err, testCase.wantErr) {
114					t.Errorf("BuildDeepLink(%q, %q) error = %v, want %v", testCase.resource, testCase.id, err, testCase.wantErr)
115				}
116
117				return
118			}
119
120			if err != nil {
121				t.Errorf("BuildDeepLink(%q, %q) unexpected error = %v", testCase.resource, testCase.id, err)
122
123				return
124			}
125
126			if got != testCase.want {
127				t.Errorf("BuildDeepLink(%q, %q) = %q, want %q", testCase.resource, testCase.id, got, testCase.want)
128			}
129		})
130	}
131}
132
133func TestValidateUUID(t *testing.T) {
134	t.Parallel()
135
136	tests := []struct {
137		name    string
138		input   string
139		wantErr bool
140	}{
141		{"valid_uuid", "12345678-1234-1234-1234-123456789012", false},
142		{"valid_uuid_lowercase", "abcdef12-3456-7890-abcd-ef1234567890", false},
143		{"valid_uuid_uppercase", "ABCDEF12-3456-7890-ABCD-EF1234567890", false},
144		{"valid_uuid_mixed", "AbCdEf12-3456-7890-AbCd-Ef1234567890", false},
145		{"empty", "", true},
146		{"invalid_format", "not-a-uuid", true},
147		{"too_short", "12345678-1234-1234-1234", true},
148		{"too_long", "12345678-1234-1234-1234-1234567890123", true},
149		{"missing_hyphens", "12345678123412341234123456789012", false}, // google/uuid accepts this
150	}
151
152	for _, testCase := range tests {
153		t.Run(testCase.name, func(t *testing.T) {
154			t.Parallel()
155
156			err := lunatask.ValidateUUID(testCase.input)
157			if (err != nil) != testCase.wantErr {
158				t.Errorf("ValidateUUID(%q) error = %v, wantErr %v", testCase.input, err, testCase.wantErr)
159			}
160
161			if testCase.wantErr && err != nil && !errors.Is(err, lunatask.ErrInvalidUUID) {
162				t.Errorf("ValidateUUID(%q) error = %v, want wrapped ErrInvalidUUID", testCase.input, err)
163			}
164		})
165	}
166}
167
168func TestResource_Valid(t *testing.T) {
169	t.Parallel()
170
171	tests := []struct {
172		name     string
173		resource lunatask.Resource
174		want     bool
175	}{
176		{"area", lunatask.ResourceArea, true},
177		{"goal", lunatask.ResourceGoal, true},
178		{"task", lunatask.ResourceTask, true},
179		{"note", lunatask.ResourceNote, true},
180		{"person", lunatask.ResourcePerson, true},
181		{"notebook", lunatask.ResourceNotebook, true},
182		{"invalid", lunatask.Resource("invalid"), false},
183		{"empty", lunatask.Resource(""), false},
184	}
185
186	for _, testCase := range tests {
187		t.Run(testCase.name, func(t *testing.T) {
188			t.Parallel()
189
190			if got := testCase.resource.Valid(); got != testCase.want {
191				t.Errorf("Resource(%q).Valid() = %v, want %v", testCase.resource, got, testCase.want)
192			}
193		})
194	}
195}
196
197func TestResource_String(t *testing.T) {
198	t.Parallel()
199
200	tests := []struct {
201		name     string
202		resource lunatask.Resource
203		want     string
204	}{
205		{"area", lunatask.ResourceArea, "areas"},
206		{"goal", lunatask.ResourceGoal, "goals"},
207		{"task", lunatask.ResourceTask, "tasks"},
208		{"note", lunatask.ResourceNote, "notes"},
209		{"person", lunatask.ResourcePerson, "people"},
210		{"notebook", lunatask.ResourceNotebook, "notebooks"},
211	}
212
213	for _, testCase := range tests {
214		t.Run(testCase.name, func(t *testing.T) {
215			t.Parallel()
216
217			if got := testCase.resource.String(); got != testCase.want {
218				t.Errorf("Resource.String() = %q, want %q", got, testCase.want)
219			}
220		})
221	}
222}