1package deletion
2
3import (
4	"testing"
5	"time"
6
7	"github.com/prometheus/common/model"
8	"github.com/prometheus/prometheus/pkg/labels"
9	"github.com/stretchr/testify/require"
10
11	"github.com/grafana/loki/pkg/logql"
12	"github.com/grafana/loki/pkg/storage/stores/shipper/compactor/retention"
13)
14
15func TestDeleteRequest_IsDeleted(t *testing.T) {
16	now := model.Now()
17	user1 := "user1"
18
19	lbls := `{foo="bar", fizz="buzz"}`
20
21	chunkEntry := retention.ChunkEntry{
22		ChunkRef: retention.ChunkRef{
23			UserID:  []byte(user1),
24			From:    now.Add(-3 * time.Hour),
25			Through: now.Add(-time.Hour),
26		},
27		Labels: mustParseLabel(lbls),
28	}
29
30	type resp struct {
31		isDeleted           bool
32		nonDeletedIntervals []model.Interval
33	}
34
35	for _, tc := range []struct {
36		name          string
37		deleteRequest DeleteRequest
38		expectedResp  resp
39	}{
40		{
41			name: "whole chunk deleted",
42			deleteRequest: DeleteRequest{
43				UserID:    user1,
44				StartTime: now.Add(-3 * time.Hour),
45				EndTime:   now.Add(-time.Hour),
46				Selectors: []string{lbls},
47			},
48			expectedResp: resp{
49				isDeleted:           true,
50				nonDeletedIntervals: nil,
51			},
52		},
53		{
54			name: "chunk deleted from beginning",
55			deleteRequest: DeleteRequest{
56				UserID:    user1,
57				StartTime: now.Add(-3 * time.Hour),
58				EndTime:   now.Add(-2 * time.Hour),
59				Selectors: []string{lbls},
60			},
61			expectedResp: resp{
62				isDeleted: true,
63				nonDeletedIntervals: []model.Interval{
64					{
65						Start: now.Add(-2*time.Hour) + 1,
66						End:   now.Add(-time.Hour),
67					},
68				},
69			},
70		},
71		{
72			name: "chunk deleted from end",
73			deleteRequest: DeleteRequest{
74				UserID:    user1,
75				StartTime: now.Add(-2 * time.Hour),
76				EndTime:   now,
77				Selectors: []string{lbls},
78			},
79			expectedResp: resp{
80				isDeleted: true,
81				nonDeletedIntervals: []model.Interval{
82					{
83						Start: now.Add(-3 * time.Hour),
84						End:   now.Add(-2*time.Hour) - 1,
85					},
86				},
87			},
88		},
89		{
90			name: "chunk deleted from end",
91			deleteRequest: DeleteRequest{
92				UserID:    user1,
93				StartTime: now.Add(-2 * time.Hour),
94				EndTime:   now,
95				Selectors: []string{lbls},
96			},
97			expectedResp: resp{
98				isDeleted: true,
99				nonDeletedIntervals: []model.Interval{
100					{
101						Start: now.Add(-3 * time.Hour),
102						End:   now.Add(-2*time.Hour) - 1,
103					},
104				},
105			},
106		},
107		{
108			name: "chunk deleted in the middle",
109			deleteRequest: DeleteRequest{
110				UserID:    user1,
111				StartTime: now.Add(-(2*time.Hour + 30*time.Minute)),
112				EndTime:   now.Add(-(time.Hour + 30*time.Minute)),
113				Selectors: []string{lbls},
114			},
115			expectedResp: resp{
116				isDeleted: true,
117				nonDeletedIntervals: []model.Interval{
118					{
119						Start: now.Add(-3 * time.Hour),
120						End:   now.Add(-(2*time.Hour + 30*time.Minute)) - 1,
121					},
122					{
123						Start: now.Add(-(time.Hour + 30*time.Minute)) + 1,
124						End:   now.Add(-time.Hour),
125					},
126				},
127			},
128		},
129		{
130			name: "delete request out of range",
131			deleteRequest: DeleteRequest{
132				UserID:    user1,
133				StartTime: now.Add(-12 * time.Hour),
134				EndTime:   now.Add(-10 * time.Hour),
135				Selectors: []string{lbls},
136			},
137			expectedResp: resp{
138				isDeleted: false,
139			},
140		},
141		{
142			name: "request not matching due to matchers",
143			deleteRequest: DeleteRequest{
144				UserID:    user1,
145				StartTime: now.Add(-3 * time.Hour),
146				EndTime:   now.Add(-time.Hour),
147				Selectors: []string{`{foo1="bar"}`, `{fizz1="buzz"}`},
148			},
149			expectedResp: resp{
150				isDeleted: false,
151			},
152		},
153		{
154			name: "request for a different user",
155			deleteRequest: DeleteRequest{
156				UserID:    "user2",
157				StartTime: now.Add(-3 * time.Hour),
158				EndTime:   now.Add(-time.Hour),
159				Selectors: []string{lbls},
160			},
161			expectedResp: resp{
162				isDeleted: false,
163			},
164		},
165	} {
166		t.Run(tc.name, func(t *testing.T) {
167			isDeleted, nonDeletedIntervals := tc.deleteRequest.IsDeleted(chunkEntry)
168			require.Equal(t, tc.expectedResp.isDeleted, isDeleted)
169			require.Equal(t, tc.expectedResp.nonDeletedIntervals, nonDeletedIntervals)
170		})
171	}
172}
173
174func mustParseLabel(input string) labels.Labels {
175	lbls, err := logql.ParseLabels(input)
176	if err != nil {
177		panic(err)
178	}
179
180	return lbls
181}
182